¿Cómo se hace una copia profunda de un objeto en .NET (C # específicamente)?

Quiero una verdadera copia profunda. En Java, esto fue fácil, pero ¿cómo lo haces en C #?

He visto algunos enfoques diferentes para esto, pero utilizo un método de utilidad genérico como tal:

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); } } 

Notas:

  • Tu clase DEBE estar marcada como [Serializable] para que funcione.
  • Su archivo fuente debe incluir el siguiente código:

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

Escribí un método de extensión de copia de objetos profundos , basado en el recursivo “MemberwiseClone” . Es rápido ( tres veces más rápido que BinaryFormatter) y funciona con cualquier objeto. No necesita un constructor por defecto o atributos serializables.

Basándose en la solución de Kilhoffer …

Con C # 3.0 puede crear un método de extensión de la siguiente manera:

 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); } } } 

que extiende cualquier clase que haya sido marcada como [Serializable] con un método DeepClone

 MyClass copy = obj.DeepClone(); 

Puede usar Nested MemberwiseClone para hacer una copia profunda . Es casi la misma velocidad que copiar una estructura de valor, y es un orden de magnitud más rápido que (a) reflexión o (b) serialización (como se describe en otras respuestas en esta página).

Tenga en cuenta que si usa Nested MemberwiseClone para una copia profunda , debe implementar manualmente una ShallowCopy para cada nivel nested en la clase, y una DeepCopy que invoque todos los métodos de ShallowCopy para crear una copia completa. Esto es simple: solo unas pocas líneas en total, vea el código de demostración a continuación.

Aquí está el resultado del código que muestra la diferencia relativa de rendimiento (4,77 segundos para DeepwiseMedición anidada profunda frente a 39,93 segundos para la serialización). Usar MemberwiseCopy nested es casi tan rápido como copiar una estructura, y copiar una estructura es bastante cercano a la velocidad máxima teórica que es capaz de .NET.

  Demo of shallow and deep copy, using classes 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 

Para entender cómo hacer una copia profunda usando MemberwiseCopy, aquí está el proyecto de demostración:

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

Luego, llame a la demostración desde main:

  void MyMain(string[] args) { { Console.Write("Demo of shallow and deep copy, using classes 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(); } 

De nuevo, tenga en cuenta que si usa Nested MemberwiseClone para una copia profunda , debe implementar manualmente una ShallowCopy para cada nivel nested en la clase, y una DeepCopy que invoque todos los métodos de ShallowCopy para crear una copia completa. Esto es simple: solo unas pocas líneas en total, vea el código de demostración anterior.

Tenga en cuenta que cuando se trata de clonar un objeto, existe una gran diferencia entre una “estructura” y una “clase”:

  • Si tiene una “estructura”, es un tipo de valor para que pueda copiarlo y los contenidos se clonarán.
  • Si tiene una “clase”, es un tipo de referencia, de modo que si la copia, todo lo que está haciendo es copiar el puntero a ella. Para crear un clon verdadero, debe ser más creativo y usar un método que cree otra copia del objeto original en la memoria.
  • La clonación de objetos de forma incorrecta puede conducir a errores muy difíciles de precisar. En el código de producción, tiendo a implementar una sum de verificación para verificar que el objeto ha sido clonado correctamente y que no ha sido dañado por otra referencia al mismo. Esta sum de comprobación se puede desactivar en el modo de lanzamiento.
  • Encuentro que este método es bastante útil: a menudo, solo quieres clonar partes del objeto, no todo. También es esencial para cualquier caso de uso en el que se modifiquen objetos, y luego se introducen las copias modificadas en una cola.

Actualizar

Probablemente sea posible utilizar la reflexión para recorrer recursivamente el gráfico del objeto y hacer una copia profunda. WCF usa esta técnica para serializar un objeto, incluidos todos sus elementos secundarios. El truco es anotar todos los objetos secundarios con un atributo que lo hace reconocible. Sin embargo, es posible que pierda algunos beneficios de rendimiento.

Actualizar

Cita en la prueba de velocidad independiente (ver comentarios a continuación):

He realizado mi propia prueba de velocidad con el método de extensión de serialización / deserialización de Neil, Contango’s Neseted MemberwiseClone, el método de extensión basado en la reflexión de Alex Burtsev y AutoMapper, 1 millón de veces cada uno. Serialize-deserialize fue más lento, tomando 15.7 segundos. Luego vino AutoMapper, tomando 10.1 segundos. Mucho más rápido fue el método basado en la reflexión, que tardó 2.4 segundos. Por mucho, el más rápido fue nested MemberwiseClone, tomando 0,1 segundos. Se reduce al rendimiento versus la molestia de agregar código a cada clase para clonarlo. Si el rendimiento no es un problema, vaya con el método de Alex Burtsev. – Simon Tewsi

Creo que el enfoque BinaryFormatter es relativamente lento (¡lo cual fue una sorpresa para mí!). Es posible que pueda usar ProtoBuf .NET para algunos objetos si cumplen con los requisitos de ProtoBuf. Desde la página de introducción de ProtoBuf ( http://code.google.com/p/protobuf-net/wiki/GettingStarted ):

Notas sobre tipos soportados:

Clases personalizadas que:

  • Están marcados como contrato de datos
  • Tener un constructor sin parámetros
  • Para Silverlight: son públicos
  • Muchos primitivos comunes, etc.
  • Arrays de dimensión única : T []
  • Lista / IList
  • Diccionario / IDictionary
  • cualquier tipo que implemente IEnumerable y tenga un método Add (T)

El código asume que los tipos serán mutables alrededor de los miembros elegidos. En consecuencia, las estructuras personalizadas no son compatibles, ya que deben ser inmutables.

Si su clase cumple con estos requisitos, puede intentar:

 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); } } 

Lo cual es MUY rápido de hecho …

Editar:

Aquí está el código de trabajo para una modificación de esto (probado en .NET 4.6). Utiliza System.Xml.Serialization y System.IO. No es necesario marcar las clases como serializables.

 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); } } 

Tal vez solo necesites una copia superficial, en ese caso usa Object.MemberWiseClone() .

Hay buenas recomendaciones en la documentación de MemberWiseClone() para estrategias de copia profunda:

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

Puedes intentar esto

  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"); } 

Gracias al artículo de DetoX83 sobre el proyecto de código.

La mejor manera es:

  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 documentación de MSDN parece indicar que Clone debe realizar una copia profunda, pero nunca se indica explícitamente:

La interfaz ICloneable contiene un miembro, Clone, que es compatible con la clonación más allá de la proporcionada por MemberWiseClone … El método MemberwiseClone crea una copia superficial …

Puedes encontrar mi publicación útil.

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; } } 

De esta manera es unas veces más rápido que BinarySerialization Y esto no requiere el atributo [Serializable] .

Tengo una idea más simple. Use LINQ con una nueva selección.

 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}; }