2009-02-20 9 views
10

Ho un requisito per mappare tutti i valori dei campi e le raccolte figlio tra ObjectV1 e ObjectV2 in base al nome del campo. ObjectV2 si trova in un namspace diverso da ObjectV1.Come eseguire il deep copy tra oggetti di tipi diversi in C# .NET

L'ereditarietà tra il modello ClassV1 e ClassV2 è stata scontata poiché queste 2 classi devono evolversi indipendentemente. Ho preso in considerazione l'utilizzo sia della riflessione (che è lenta) che della serializzazione binaria (che è anche lenta) per eseguire la mappatura delle proprietà comuni.

Esiste un approccio preferenziale? Ci sono altre alternative?

+2

ottenere alcune metriche effettive sull'approccio basato sulla riflessione. Potresti scoprire che le prestazioni del mondo reale sono perfettamente adeguate - l'ho fatto! In caso contrario, segui la strada suggerita da Chris Ballard ... – kpollock

risposta

6

In alternativa all'utilizzo del reflection ogni volta, è possibile creare una classe helper che crea dinamicamente i metodi di copia utilizzando Reflection.Emit - questo significherebbe solo ottenere il risultato di prestazioni all'avvio. Questo potrebbe darti la combinazione di flessibilità e prestazioni di cui hai bisogno.

Come Reflection.Emit è piuttosto goffo, suggerirei di verificare Addin Reflector this, che è brillante per la creazione di questo tipo di codice.

0

Se la velocità è un problema, è necessario implementare metodi di clonazione nei metodi stessi.

+0

Buona idea, ma questo introduce una dipendenza tra le classi che sto cercando di evitare. Grazie. –

+0

Non necessariamente. È possibile clonare lo stato in un formato generico. le classi dovrebbero solo essere a conoscenza del protocollo comune. –

4

Quale versione di .NET è?

Per copia superficiale:

In 3.5, è possibile pre-compilare un Expression per fare questo. In 2.0, puoi usare lo HyperDescriptor molto facilmente per fare lo stesso. Entrambi supereranno ampiamente la riflessione.

Ci

è un'implementazione pre-scatola dell'approccio Expression in MiscUtil-PropertyCopy:

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

(parte bassa)

BinaryFormatter (in questione) non è un'opzione qui - semplicemente non funzionerà poiché il tipo di destinazione e quello originale sono diversi. Se i dati sono basati sul contratto, XmlSerializer o DataContractSerializer funzionerebbero se corrispondono a tutti i nomi dei contratti, ma le due opzioni (superficiali) di cui sopra sarebbero molto più veloci se possibile.

Inoltre - se i vostri tipi sono contrassegnati con gli attributi di serializzazione comuni (XmlType o DataContract), quindi protobuf-netpuò (in alcuni casi) non una profonda-copy/cambio-tipo per voi:

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

Ma questo dipende dai tipi che hanno schemi molto simili (in effetti, non usa i nomi, usa l'esplicito "Ordine" ecc sugli attributi)

+0

Puoi fornire alcuni esempi di codice su come eseguire le espressioni pre-compilate e HyperDescriptor? – Svish

+0

Ho aggiunto il 1-liner per PropertyCopy, ma attenzione (aggiornato): questo farà una copia * shallow *. Avrebbe bisogno di un certo sforzo per fare una copia profonda (anzi, la copia profonda semplicemente non è sempre possibile). –

1

Se la velocità è un problema, si può prendere il processo di riflessione offline e generare il codice per la mappatura del com proprietà mon. È possibile farlo a runtime utilizzando Generazione codice leggero o completamente offline creando un codice C# per la compilazione.

1

Se si controlla l'istanza dell'oggetto di destinazione, provare a utilizzare JavaScriptSerializer. Non sputa nessun tipo di informazione.

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

rendimenti

{Id: 1, Name: "A"} 

Da questo dovrebbe possibile deserializzare qualsiasi classe con gli stessi nomi di proprietà.

2

Ecco una soluzione che ho costruito:

 /// <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

Ehilà, ho una domanda riguardante il tuo interessante frammento di codice. Come gestiresti la dupplicazione di un oggetto all'interno di un obiettivo nullo? Ottengo un'eccezione se ci provo. –

3

Si potrebbe voler dare un'occhiata a AutoMapper, una biblioteca specializzata nel copiare i valori tra gli oggetti. Usa la convenzione sulla configurazione, quindi se le proprietà hanno davvero gli stessi nomi exaxt farà quasi tutto il lavoro per te.

+0

Vorrei poter +2 o +3 questo :) – jao