Cómo hacer una copia profunda de un objeto in.NET (C # específicamente)? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Quiero una copia profunda verdadera. En Java, esto era fácil, pero ¿cómo se hace en C#?

Author: Peter Mortensen, 2008-09-24

11 answers

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

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

   return (T) formatter.Deserialize(ms);
 }
}

Notas:

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

    using System.Runtime.Serialization.Formatters.Binary;
    using System.IO;
    
 542
Author: Kilhoffer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-06 07:27:38

I escribió un método de extensión de copia de objeto profundo, basado en recursivo "MemberwiseClone". Es rápido (tres veces más rápido que BinaryFormatter), y funciona con cualquier objeto. No necesita un constructor predeterminado ni atributos serializables.

 256
Author: Alex Burtsev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-02 21:48:39

Basado 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<T>(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 ha sido marcada como[Serializable] con un método de DeepClone

MyClass copy = obj.DeepClone();
 152
Author: Neil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-26 15:03:41

Puede usar MemberwiseClone anidado 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 utiliza MemberwiseClone anidado para una copia profunda, debe implementar manualmente un ShallowCopy para cada nivel anidado en la clase, y un DeepCopy que llama a todos los métodos ShallowCopy mencionados para crear una copia completa clon. Esto es simple: solo unas pocas líneas en total, vea el código de demostración a continuación.

Aquí está la salida del código que muestra la diferencia de rendimiento relativa (4.77 segundos para MemberwiseCopy anidado profundo vs.39.93 segundos para Serialización). Usar MemberwiseCopy anidado es casi tan rápido como copiar una estructura, y copiar una estructura está bastante cerca de la velocidad máxima teórica de la que.NET es capaz.

    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á la demostración proyecto:

// 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>(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, llama a la demo 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<Person>(Bob);
                total += BobsSon.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        Console.ReadKey();
    }

De nuevo, tenga en cuenta que si utiliza MemberwiseClone anidado para una copia profunda, debe implementar manualmente un ShallowCopy para cada nivel anidado en la clase, y un DeepCopy que llama a todos los métodos ShallowCopy para crear un clon completo. 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, hay una gran diferencia entre "struct" y una "clase":

  • Si tiene una "estructura", es un tipo de valor, por lo que puede copiarla y el contenido se clonará.
  • Si tiene una "clase", es un tipo de referencia, por lo que si la copia, todo lo que está haciendo es copiar el puntero a ella. Para crear un clon verdadero, tienes que ser más creativo, y usar un método que cree otra copia del objeto original en memoria.
  • Clonar objetos incorrectamente puede llevar a errores muy difíciles de identificar. En producción código, tiendo a implementar una suma de comprobación para comprobar que el objeto ha sido clonado correctamente, y no ha sido dañado por otra referencia a él. Esta suma de comprobación se puede desactivar en el modo Release.
  • Encuentro este método bastante útil: a menudo, solo quieres clonar partes del objeto, no todo. También es esencial para cualquier caso de uso en el que esté modificando objetos y luego introduciendo las copias modificadas en una cola.

Update

Probablemente es posible utilizar la reflexión para recorrer recursivamente el gráfico de objetos para hacer una copia profunda. WCF utiliza esta técnica para serializar un objeto, incluyendo todos sus hijos. 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.

Update

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

He realizado mi propia prueba de velocidad usando serialize/deserialize de Neil método de extensión, Contango Anidada MemberwiseClone, Alex Burtsev del método de extensión basado en reflexión y AutoMapper, 1 millón de veces cada. Serializar-deserializar fue más lento, tomando 15.7 segundos. Entonces llegó AutoMapper, tardando 10,1 segundos. Mucho más rápido fue el método basado en la reflexión que tomó 2,4 segundos. Con mucho, el más rápido fue MemberwiseClone anidado, toma 0.1 segundos. Se reduce al rendimiento versus la molestia de agregar código a cada clase para clonarlo. Rendimiento de If no es un problema ir con el método de Alex Burtsev. - Simon Tewsi

 46
Author: Contango,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-08 16:32:16

Creo que el enfoque BinaryFormatter es relativamente lento (¡lo que me sorprendió!). 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 los tipos soportados:

Clases personalizadas que:

  • Se marcan como data-contract
  • Tienen un sin parámetros constructor
  • Para Silverlight: son públicos
  • Muchos primitivos comunes, etc.
  • Matrices de dimensiones simples: T []
  • List / IList
  • Dictionary / IDictionary
  • cualquier tipo que implementeerableumerable 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 inmutable.

Si tu clase cumple con estos requisitos puedes probar:

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

Que 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 el Sistema.XML.Serialización y System.IO. No es necesario marcar las clases como serializables.

public void DeepCopy<T>(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);
    }
}
 13
Author: Kurt Richardson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-20 12:03:59

Puedes probar 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 a DetoX83 artículo en el proyecto de código.

 6
Author: Suresh Kumar Veluswamy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-01 03:59:40

Tal vez solo necesite una copia superficial, en ese caso use 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

 5
Author: David Thornley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-04 19:33:54

La mejor manera es:

    public interface IDeepClonable<T> where T : class
    {
        T DeepClone();
    }

    public class MyObj : IDeepClonable<MyObj>
    {
        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];
    }
 3
Author: alex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-02 21:47:19

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 está destinado a soportar la clonación más allá de la suministrada por MemberwiseClone The El método MemberwiseClone crea una copia superficial {

Puede encontrar mi publicación útil.

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

 0
Author: Eugene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-13 13:50:01
    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 un par de veces más rápido que BinarySerialization Y esto no requiere el atributo [Serializable].

 0
Author: Basil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-17 21:11:28

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

public class Fruit
{
  public string Name {get; set;}
  public int SeedCount {get; set;}
}

void SomeMethod()
{
  List<Fruit> originalFruits = new List<Fruit>();
  originalFruits.Add(new Fruit {Name="Apple", SeedCount=10});
  originalFruits.Add(new Fruit {Name="Banana", SeedCount=0});

  //Deep Copy
  List<Fruit> deepCopiedFruits = from f in originalFruits
              select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
}
 -5
Author: Jordan Morris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-02 21:49:50