Come si fa una copia profonda di un object in .NET (in particolare C #)?

Voglio una vera copia profonda. In Java, questo è stato facile, ma come si fa in C #?

Ho visto alcuni approcci diversi a questo, ma io uso un metodo di utilità generico in quanto tale:

public static T DeepClone(T obj) { using (var ms = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(ms, obj); ms.Position = 0; return (T) formatter.Deserialize(ms); } } 

Gli appunti:

  • La tua class DEVE essere contrassegnata come [Serializable] affinché funzioni.
  • Il tuo file sorgente deve includere il seguente codice:

     using System.Runtime.Serialization.Formatters.Binary; using System.IO; 

Ho scritto un metodo di estensione della copia di oggetti profondi , basato su “MemberwiseClone” ricorsivo. È veloce ( tre volte più veloce di BinaryFormatter) e funziona con qualsiasi object. Non è necessario un costruttore predefinito o attributi serializzabili.

Costruire sulla soluzione di Kilhoffer …

Con C # 3.0 puoi creare un metodo di estensione come segue:

 public static class ExtensionMethods { // Deep clone public static T DeepClone(this T a) { using (MemoryStream stream = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, a); stream.Position = 0; return (T) formatter.Deserialize(stream); } } } 

che estende qualsiasi class che è stata contrassegnata come [Serializable] con un metodo DeepClone

 MyClass copy = obj.DeepClone(); 

Puoi usare Nested MemberwiseClone per fare una copia profonda . Ha quasi la stessa velocità con cui copia una struct value, ed è un ordine di grandezza più veloce di (a) reflection o (b) serializzazione (come descritto in altre risposte in questa pagina).

Si noti che se si utilizza Nested MemberwiseClone per una copia profonda , è necessario implementare manualmente un ShallowCopy per ogni livello nidificato nella class e un DeepCopy che chiama tutti i suddetti metodi di ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo qui sotto.

Ecco l’output del codice che mostra la differenza relativa delle prestazioni (4,77 secondi per MemberwiseCopy nidificato profondo contro 39,93 secondi per Serializzazione). Usare nested MemberwiseCopy è quasi veloce come copiare una struct, e copiare una struct è dannatamente vicino alla velocità massima teorica di cui .NET è capace.

  Demo of shallow and deep copy, using classs and MemberwiseClone: Create Bob Bob.Age=30, Bob.Purchase.Description=Lamborghini Clone Bob >> BobsSon Adjust BobsSon details BobsSon.Age=2, BobsSon.Purchase.Description=Toy car Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob: Bob.Age=30, Bob.Purchase.Description=Lamborghini Elapsed time: 00:00:04.7795670,30000000 Demo of shallow and deep copy, using structs and value copying: Create Bob Bob.Age=30, Bob.Purchase.Description=Lamborghini Clone Bob >> BobsSon Adjust BobsSon details: BobsSon.Age=2, BobsSon.Purchase.Description=Toy car Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob: Bob.Age=30, Bob.Purchase.Description=Lamborghini Elapsed time: 00:00:01.0875454,30000000 Demo of deep copy, using class and serialize/deserialize: Elapsed time: 00:00:39.9339425,30000000 

Per capire come fare una copia profonda usando MemberwiseCopy, ecco il progetto demo:

 // Nested MemberwiseClone example. // Added to demo how to deep copy a reference class. [Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization. public class Person { public Person(int age, string description) { this.Age = age; this.Purchase.Description = description; } [Serializable] // Not required if using MemberwiseClone public class PurchaseType { public string Description; public PurchaseType ShallowCopy() { return (PurchaseType)this.MemberwiseClone(); } } public PurchaseType Purchase = new PurchaseType(); public int Age; // Add this if using nested MemberwiseClone. // This is a class, which is a reference type, so cloning is more difficult. public Person ShallowCopy() { return (Person)this.MemberwiseClone(); } // Add this if using nested MemberwiseClone. // This is a class, which is a reference type, so cloning is more difficult. public Person DeepCopy() { // Clone the root ... Person other = (Person) this.MemberwiseClone(); // ... then clone the nested class. other.Purchase = this.Purchase.ShallowCopy(); return other; } } // Added to demo how to copy a value struct (this is easy - a deep copy happens by default) public struct PersonStruct { public PersonStruct(int age, string description) { this.Age = age; this.Purchase.Description = description; } public struct PurchaseType { public string Description; } public PurchaseType Purchase; public int Age; // This is a struct, which is a value type, so everything is a clone by default. public PersonStruct ShallowCopy() { return (PersonStruct)this; } // This is a struct, which is a value type, so everything is a clone by default. public PersonStruct DeepCopy() { return (PersonStruct)this; } } // Added only for a speed comparison. public class MyDeepCopy { public static T DeepCopy(T obj) { object result = null; using (var ms = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(ms, obj); ms.Position = 0; result = (T)formatter.Deserialize(ms); ms.Close(); } return (T)result; } } 

Quindi, chiama la demo dal principale:

  void MyMain(string[] args) { { Console.Write("Demo of shallow and deep copy, using classs and MemberwiseCopy:\n"); var Bob = new Person(30, "Lamborghini"); Console.Write(" Create Bob\n"); Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description); Console.Write(" Clone Bob >> BobsSon\n"); var BobsSon = Bob.DeepCopy(); Console.Write(" Adjust BobsSon details\n"); BobsSon.Age = 2; BobsSon.Purchase.Description = "Toy car"; Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description); Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n"); Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description); Debug.Assert(Bob.Age == 30); Debug.Assert(Bob.Purchase.Description == "Lamborghini"); var sw = new Stopwatch(); sw.Start(); int total = 0; for (int i = 0; i < 100000; i++) { var n = Bob.DeepCopy(); total += n.Age; } Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total); } { Console.Write("Demo of shallow and deep copy, using structs:\n"); var Bob = new PersonStruct(30, "Lamborghini"); Console.Write(" Create Bob\n"); Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description); Console.Write(" Clone Bob >> BobsSon\n"); var BobsSon = Bob.DeepCopy(); Console.Write(" Adjust BobsSon details:\n"); BobsSon.Age = 2; BobsSon.Purchase.Description = "Toy car"; Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description); Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n"); Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description); Debug.Assert(Bob.Age == 30); Debug.Assert(Bob.Purchase.Description == "Lamborghini"); var sw = new Stopwatch(); sw.Start(); int total = 0; for (int i = 0; i < 100000; i++) { var n = Bob.DeepCopy(); total += n.Age; } Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total); } { Console.Write("Demo of deep copy, using class and serialize/deserialize:\n"); int total = 0; var sw = new Stopwatch(); sw.Start(); var Bob = new Person(30, "Lamborghini"); for (int i = 0; i < 100000; i++) { var BobsSon = MyDeepCopy.DeepCopy(Bob); total += BobsSon.Age; } Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total); } Console.ReadKey(); } 

Di nuovo, si noti che se si utilizza Nested MemberwiseClone per una copia profonda , è necessario implementare manualmente ShallowCopy per ogni livello nidificato nella class e un DeepCopy che chiama tutti i suddetti metodi di ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo qui sopra.

Nota che quando si tratta di clonare un object, c’è una grande differenza tra una “struct” e una “class”:

  • Se hai una “struct”, è un tipo di valore, quindi puoi semplicemente copiarlo e il contenuto sarà clonato.
  • Se hai una “class”, è un tipo di riferimento, quindi se lo copi, tutto ciò che stai facendo è copiare il puntatore su di esso. Per creare un vero clone, devi essere più creativo e utilizzare un metodo che crei un’altra copia dell’object originale nella memoria.
  • La clonazione non corretta degli oggetti può portare a bug molto difficili da decifrare. Nel codice di produzione, tendo ad implementare un checksum per verificare che l’object sia stato clonato correttamente e non sia stato danneggiato da un altro riferimento ad esso. Questo checksum può essere distriggersto in modalità di rilascio.
  • Trovo abbastanza utile questo metodo: spesso, vuoi solo clonare parti dell’object, non l’intera cosa. È anche essenziale per qualsiasi caso d’uso in cui si stanno modificando oggetti, quindi si inseriscono le copie modificate in una coda.

Aggiornare

Probabilmente è ansible utilizzare la riflessione per camminare in modo ricorsivo nel grafico dell’object per eseguire una copia profonda. WCF utilizza questa tecnica per serializzare un object, inclusi tutti i suoi figli. Il trucco è di annotare tutti gli oggetti figlio con un attributo che lo rende individuabile. Potresti perdere alcuni benefici prestazionali, comunque.

Aggiornare

Citazione su test di velocità indipendente (vedi commenti sotto):

Ho eseguito il mio test di velocità utilizzando il metodo di estensione serialize / deserialize di Neil, il membro MemberwiseClone di Contango, il metodo di estensione basato sulla riflessione di Alex Burtsev e AutoMapper, 1 milione di volte ciascuno. Serializzare-deserializzare era più lento, impiegando 15,7 secondi. Poi è arrivato AutoMapper, prendendo 10,1 secondi. Molto più veloce era il metodo basato sulla riflessione che impiegava 2,4 secondi. Di gran lunga il più veloce è stato Nested MemberwiseClone, prendendo 0,1 secondi. Arriva alle prestazioni rispetto al fastidio di aggiungere codice a ogni class per clonarlo. Se le prestazioni non sono un problema, vai con il metodo di Alex Burtsev. – Simon Tewsi

Credo che l’approccio di BinaryFormatter sia relativamente lento (che per me è stata una sorpresa!). Potresti essere in grado di utilizzare ProtoBuf .NET per alcuni oggetti se soddisfano i requisiti di ProtoBuf. Dalla pagina ProtoBuf introduttiva ( http://code.google.com/p/protobuf-net/wiki/GettingStarted ):

Note sui tipi supportati:

Classi personalizzate che:

  • Sono contrassegnati come dati-contratto
  • Avere un costruttore senza parametri
  • Per Silverlight: sono pubblici
  • Molti primitivi comuni, ecc.
  • Matrici monodesmensionali: T []
  • Elenco / IList
  • Dizionario / IDictionary
  • qualsiasi tipo che implementa IEnumerable e ha un metodo Add (T)

Il codice presuppone che i tipi saranno mutabili attorno ai membri eletti. Di conseguenza, le strutture personalizzate non sono supportate, poiché dovrebbero essere immutabili.

Se la tua class soddisfa questi requisiti puoi provare:

 public static void deepCopy(ref T object2Copy, ref T objectCopy) { using (var stream = new MemoryStream()) { Serializer.Serialize(stream, object2Copy); stream.Position = 0; objectCopy = Serializer.Deserialize(stream); } } 

Il che è MOLTO davvero veloce …

Modificare:

Ecco un codice funzionante per una modifica di questo (testato su .NET 4.6). Utilizza System.Xml.Serialization e System.IO. Non è necessario contrassegnare le classi come serializzabili.

 public void DeepCopy(ref T object2Copy, ref T objectCopy) { using (var stream = new MemoryStream()) { var serializer = new XS.XmlSerializer(typeof(T)); serializer.Serialize(stream, object2Copy); stream.Position = 0; objectCopy = (T)serializer.Deserialize(stream); } } 

Forse hai solo bisogno di una copia superficiale, in questo caso usa Object.MemberWiseClone() .

Ci sono buoni consigli nella documentazione per MemberWiseClone() per le strategie da copiare in profondità: –

http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

Puoi provare questo

  public static object DeepCopy(object obj) { if (obj == null) return null; Type type = obj.GetType(); if (type.IsValueType || type == typeof(string)) { return obj; } else if (type.IsArray) { Type elementType = Type.GetType( type.FullName.Replace("[]", string.Empty)); var array = obj as Array; Array copied = Array.CreateInstance(elementType, array.Length); for (int i = 0; i < array.Length; i++) { copied.SetValue(DeepCopy(array.GetValue(i)), i); } return Convert.ChangeType(copied, obj.GetType()); } else if (type.IsClass) { object toret = Activator.CreateInstance(obj.GetType()); FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields) { object fieldValue = field.GetValue(obj); if (fieldValue == null) continue; field.SetValue(toret, DeepCopy(fieldValue)); } return toret; } else throw new ArgumentException("Unknown type"); } 

Grazie all'articolo di DetoX83 sul progetto di codice.

Il modo migliore è:

  public interface IDeepClonable where T : class { T DeepClone(); } public class MyObj : IDeepClonable { public MyObj Clone() { var myObj = new MyObj(); myObj._field1 = _field1;//value type myObj._field2 = _field2;//value type myObj._field3 = _field3;//value type if (_child != null) { myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same } int len = _array.Length; myObj._array = new MyObj[len]; // array / collection for (int i = 0; i < len; i++) { myObj._array[i] = _array[i]; } return myObj; } private bool _field1; public bool Field1 { get { return _field1; } set { _field1 = value; } } private int _field2; public int Property2 { get { return _field2; } set { _field2 = value; } } private string _field3; public string Property3 { get { return _field3; } set { _field3 = value; } } private MyObj _child; private MyObj Child { get { return _child; } set { _child = value; } } private MyObj[] _array = new MyObj[4]; } 

La documentazione MSDN sembra suggerire che Clone dovrebbe eseguire una copia profonda, ma non viene mai esplicitamente dichiarato:

L’interfaccia ICloneable contiene un membro, Clone, che ha lo scopo di supportare la clonazione oltre a quello fornito da MemberWiseClone … Il metodo MemberwiseClone crea una copia superficiale …

Puoi trovare il mio post utile.

http://pragmaticcoding.com/index.php/cloning-objects-in-c/

  public static object CopyObject(object input) { if (input != null) { object result = Activator.CreateInstance(input.GetType()); foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList)) { if (field.FieldType.GetInterface("IList", false) == null) { field.SetValue(result, field.GetValue(input)); } else { IList listObject = (IList)field.GetValue(result); if (listObject != null) { foreach (object item in ((IList)field.GetValue(input))) { listObject.Add(CopyObject(item)); } } } } return result; } else { return null; } } 

In questo modo sono alcune volte più veloci di BinarySerialization E questo non richiede l’attributo [Serializable] .

Ho un’idea più semplice. Usa LINQ con una nuova selezione.

 public class Fruit { public string Name {get; set;} public int SeedCount {get; set;} } void SomeMethod() { List originalFruits = new List(); originalFruits.Add(new Fruit {Name="Apple", SeedCount=10}); originalFruits.Add(new Fruit {Name="Banana", SeedCount=0}); //Deep Copy List deepCopiedFruits = from f in originalFruits select new Fruit {Name=f.Name, SeedCount=f.SeedCount}; }