2009-02-20 10 views
10

Tengo un requisito para asignar todos los valores de campo y las colecciones secundarias entre ObjectV1 y ObjectV2 por nombre de campo. ObjectV2 está en un namspace diferente a ObjectV1.Cómo copiar profundamente entre objetos de diferentes tipos en C# .NET

Herencia entre la plantilla ClassV1 y ClassV2 se ha descontado ya que estas 2 clases deben evolucionar de forma independiente. He considerado utilizar la reflexión (que es lenta) y la serialización binaria (que también es lenta) para realizar el mapeo de las propiedades comunes.

¿Hay un enfoque preferido? ¿Hay otras alternativas?

+2

obtener algunas métricas reales en el enfoque basado en la reflexión. Puede encontrar que el rendimiento en el mundo real es perfectamente adecuado, ¡lo hice! Si no, ve por el camino sugerido por Chris Ballard ... – kpollock

Respuesta

6

Como alternativa al uso de la reflexión cada vez, puede crear una clase auxiliar que crea dinámicamente métodos de copia mediante Reflection.Emit: esto significa que solo obtendrá el rendimiento al iniciarse. Esto puede darle la combinación de flexibilidad y rendimiento que necesita.

Como Reflection.Emit es bastante torpe, yo sugeriría revisar this Reflector addin, que es brillante para construir este tipo de código.

0

Si la velocidad es un problema, debe implementar métodos de clonación en los métodos mismos.

+0

Buena idea, pero esto introduce una dependencia entre las clases que trato de evitar. Gracias. –

+0

No necesariamente. Puede clonar el estado en un formato genérico. las clases solo tendrían que estar al tanto del protocolo común. –

4

¿Qué versión de .NET es?

Para copia superficial:

En 3.5, se puede pre-compilar un Expression para hacer esto. En 2.0, puede usar HyperDescriptor muy fácilmente para hacer lo mismo. Ambos superarán enormemente la reflexión.

Hay una aplicación pre-enlatado del enfoque Expression en MiscUtil - PropertyCopy:

DestType clone = PropertyCopy<DestType>.CopyFrom(original); 

(poco profunda final)

BinaryFormatter (en la pregunta) no es una opción aquí - simplemente no funcionará ya que los tipos originales y de destino son diferentes. Si los datos están basados ​​en contratos, XmlSerializer o DataContractSerializer funcionarían si coinciden todos los nombres de los contratos, pero las dos opciones (superficiales) anteriores serían mucho más rápidas si fuera posible.

También - si los tipos están marcados con atributos de serialización comunes (XmlType o DataContract), entonces protobuf-netpuede (en algunos casos) no a/de tipo cambio profundo-copia para usted:

DestType clone = Serializer.ChangeType<OriginalType, DestType>(original); 

Pero esto depende de que los tipos tengan esquemas muy similares (de hecho, no usa los nombres, usa el "Orden" explícito, etc. en los atributos)

+0

¿Podría dar algunos ejemplos de código sobre cómo hacer ese material de expresión precompilado e HyperDescriptor? – Svish

+0

He añadido el 1-liner para PropertyCopy, pero advertencia (actualizado): esto hará una copia * superficial *. Se necesitaría algún esfuerzo para hacer una copia profunda (de hecho, la copia profunda simplemente no siempre es posible). –

1

Si la velocidad es un problema, puede llevar el proceso de reflexión fuera de línea y generar código para el mapeo de la com propiedades mon Puede hacer esto en tiempo de ejecución usando Lightweight Code Generation o completamente fuera de línea al compilar código C# para compilar.

1

Si controla la creación de instancias del objeto de destino, intente con JavaScriptSerializer. No escupe ningún tipo de información.

new JavaScriptSerializer().Serialize(new NamespaceA.Person{Id = 1, Name = "A"}) 

vuelve

{Id: 1, Name: "A"} 

De esto se debe deserializar posible ninguna clase con los mismos nombres de las propiedades.

2

Aquí es una solución que he construido:

 /// <summary> 
     /// Copies the data of one object to another. The target object gets properties of the first. 
     /// Any matching properties (by name) are written to the target. 
     /// </summary> 
     /// <param name="source">The source object to copy from</param> 
     /// <param name="target">The target object to copy to</param> 
     public static void CopyObjectData(object source, object target) 
     { 
      CopyObjectData(source, target, String.Empty, BindingFlags.Public | BindingFlags.Instance); 
     } 

     /// <summary> 
     /// Copies the data of one object to another. The target object gets properties of the first. 
     /// Any matching properties (by name) are written to the target. 
     /// </summary> 
     /// <param name="source">The source object to copy from</param> 
     /// <param name="target">The target object to copy to</param> 
     /// <param name="excludedProperties">A comma delimited list of properties that should not be copied</param> 
     /// <param name="memberAccess">Reflection binding access</param> 
     public static void CopyObjectData(object source, object target, string excludedProperties, BindingFlags memberAccess) 
     { 
      string[] excluded = null; 
      if (!string.IsNullOrEmpty(excludedProperties)) 
      { 
       excluded = excludedProperties.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); 
      } 

      MemberInfo[] miT = target.GetType().GetMembers(memberAccess); 
      foreach (MemberInfo Field in miT) 
      { 
       string name = Field.Name; 

       // Skip over excluded properties 
       if (string.IsNullOrEmpty(excludedProperties) == false 
        && excluded.Contains(name)) 
       { 
        continue; 
       } 


       if (Field.MemberType == MemberTypes.Field) 
       { 
        FieldInfo sourcefield = source.GetType().GetField(name); 
        if (sourcefield == null) { continue; } 

        object SourceValue = sourcefield.GetValue(source); 
        ((FieldInfo)Field).SetValue(target, SourceValue); 
       } 
       else if (Field.MemberType == MemberTypes.Property) 
       { 
        PropertyInfo piTarget = Field as PropertyInfo; 
        PropertyInfo sourceField = source.GetType().GetProperty(name, memberAccess); 
        if (sourceField == null) { continue; } 

        if (piTarget.CanWrite && sourceField.CanRead) 
        { 
         object targetValue = piTarget.GetValue(target, null); 
         object sourceValue = sourceField.GetValue(source, null); 

         if (sourceValue == null) { continue; } 

         if (sourceField.PropertyType.IsArray 
          && piTarget.PropertyType.IsArray 
          && sourceValue != null) 
         { 
          CopyArray(source, target, memberAccess, piTarget, sourceField, sourceValue); 
         } 
         else 
         { 
          CopySingleData(source, target, memberAccess, piTarget, sourceField, targetValue, sourceValue); 
         } 
        } 
       } 
      } 
     } 

     private static void CopySingleData(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object targetValue, object sourceValue) 
     { 
      //instantiate target if needed 
      if (targetValue == null 
       && piTarget.PropertyType.IsValueType == false 
       && piTarget.PropertyType != typeof(string)) 
      { 
       if (piTarget.PropertyType.IsArray) 
       { 
        targetValue = Activator.CreateInstance(piTarget.PropertyType.GetElementType()); 
       } 
       else 
       { 
        targetValue = Activator.CreateInstance(piTarget.PropertyType); 
       } 
      } 

      if (piTarget.PropertyType.IsValueType == false 
       && piTarget.PropertyType != typeof(string)) 
      { 
       CopyObjectData(sourceValue, targetValue, "", memberAccess); 
       piTarget.SetValue(target, targetValue, null); 
      } 
      else 
      { 
       if (piTarget.PropertyType.FullName == sourceField.PropertyType.FullName) 
       { 
        object tempSourceValue = sourceField.GetValue(source, null); 
        piTarget.SetValue(target, tempSourceValue, null); 
       } 
       else 
       { 
        CopyObjectData(piTarget, target, "", memberAccess); 
       } 
      } 
     } 

     private static void CopyArray(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object sourceValue) 
     { 
      int sourceLength = (int)sourceValue.GetType().InvokeMember("Length", BindingFlags.GetProperty, null, sourceValue, null); 
      Array targetArray = Array.CreateInstance(piTarget.PropertyType.GetElementType(), sourceLength); 
      Array array = (Array)sourceField.GetValue(source, null); 

      for (int i = 0; i < array.Length; i++) 
      { 
       object o = array.GetValue(i); 
       object tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetElementType()); 
       CopyObjectData(o, tempTarget, "", memberAccess); 
       targetArray.SetValue(tempTarget, i); 
      } 
      piTarget.SetValue(target, targetArray, null); 
     } 
+0

Hola, tengo una pregunta con respecto a su fragmento interesante. ¿Cómo manejarías duplicar un objeto dentro de un objetivo nulo? Me sale una excepción si intento eso. –

3

Es posible que desee echar un vistazo a AutoMapper, una biblioteca especializada en la copia de valores entre los objetos. Utiliza la convención sobre la configuración, por lo que si las propiedades realmente tienen exactamente los mismos nombres, hará casi todo el trabajo por usted.

+0

Ojalá pudiera +2 o +3 esto :) – jao

Cuestiones relacionadas