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

StackOverflow https://stackoverflow.com/questions/129389

  •  02-07-2019
  •  | 
  •  

Pregunta

    

Esta pregunta ya tiene una respuesta aquí:

    
            
  •              Objetos de clonación profunda                                      43 respuestas                          
  •     
    

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

¿Fue útil?

Solución

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 marcarse como [Serializable] para que esto funcione.
  • Su archivo fuente debe incluir el siguiente código:

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

Otros consejos

I escribió un método de extensión de copia de objetos profundos , 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 o atributos serializables.

Código fuente:

using System.Collections.Generic;
using System.Reflection;
using System.ArrayExtensions;

namespace System
{
    public static class ObjectExtensions
    {
        private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);

        public static bool IsPrimitive(this Type type)
        {
            if (type == typeof(String)) return true;
            return (type.IsValueType & type.IsPrimitive);
        }

        public static Object Copy(this Object originalObject)
        {
            return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
        }
        private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
        {
            if (originalObject == null) return null;
            var typeToReflect = originalObject.GetType();
            if (IsPrimitive(typeToReflect)) return originalObject;
            if (visited.ContainsKey(originalObject)) return visited[originalObject];
            if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
            var cloneObject = CloneMethod.Invoke(originalObject, null);
            if (typeToReflect.IsArray)
            {
                var arrayType = typeToReflect.GetElementType();
                if (IsPrimitive(arrayType) == false)
                {
                    Array clonedArray = (Array)cloneObject;
                    clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
                }

            }
            visited.Add(originalObject, cloneObject);
            CopyFields(originalObject, visited, cloneObject, typeToReflect);
            RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
            return cloneObject;
        }

        private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
        {
            if (typeToReflect.BaseType != null)
            {
                RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
                CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
            }
        }

        private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
        {
            foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
            {
                if (filter != null && filter(fieldInfo) == false) continue;
                if (IsPrimitive(fieldInfo.FieldType)) continue;
                var originalFieldValue = fieldInfo.GetValue(originalObject);
                var clonedFieldValue = InternalCopy(originalFieldValue, visited);
                fieldInfo.SetValue(cloneObject, clonedFieldValue);
            }
        }
        public static T Copy<T>(this T original)
        {
            return (T)Copy((Object)original);
        }
    }

    public class ReferenceEqualityComparer : EqualityComparer<Object>
    {
        public override bool Equals(object x, object y)
        {
            return ReferenceEquals(x, y);
        }
        public override int GetHashCode(object obj)
        {
            if (obj == null) return 0;
            return obj.GetHashCode();
        }
    }

    namespace ArrayExtensions
    {
        public static class ArrayExtensions
        {
            public static void ForEach(this Array array, Action<Array, int[]> action)
            {
                if (array.LongLength == 0) return;
                ArrayTraverse walker = new ArrayTraverse(array);
                do action(array, walker.Position);
                while (walker.Step());
            }
        }

        internal class ArrayTraverse
        {
            public int[] Position;
            private int[] maxLengths;

            public ArrayTraverse(Array array)
            {
                maxLengths = new int[array.Rank];
                for (int i = 0; i < array.Rank; ++i)
                {
                    maxLengths[i] = array.GetLength(i) - 1;
                }
                Position = new int[array.Rank];
            }

            public bool Step()
            {
                for (int i = 0; i < Position.Length; ++i)
                {
                    if (Position[i] < maxLengths[i])
                    {
                        Position[i]++;
                        for (int j = 0; j < i; j++)
                        {
                            Position[j] = 0;
                        }
                        return true;
                    }
                }
                return false;
            }
        }
    }

}

Aprovechando 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 amplía cualquier clase que se haya marcado 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 valores, 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 anidado en la clase, y una DeepCopy que llama a todos dichos 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 a continuación.

Aquí está la salida del código que muestra la diferencia de rendimiento relativa (4.77 segundos para MemberwiseCopy anidado profundo frente a 39.93 segundos para la serialización). El uso de 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 es capaz .NET, que probablemente esté bastante cerca de la velocidad de la misma cosa en C o C ++ (pero sería tiene que ejecutar algunos puntos de referencia equivalentes para verificar este reclamo).

    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 comprender cómo hacer una copia profunda con 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>(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<Person>(Bob);
                total += BobsSon.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        Console.ReadKey();
    }

Nuevamente, tenga en cuenta que si usa Nested MemberwiseClone para una copia profunda , debe implementar manualmente una ShallowCopy para cada nivel anidado en la clase, y una DeepCopy que llama a todos los métodos de 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 un " struct " y una " clase " ;:

  • Si tiene un " struct " ;, es un tipo de valor, por lo que puede copiarlo y el contenido se clonará.
  • Si tiene una " class " ;, es un tipo de referencia, por lo que si la copia, todo lo que está haciendo es copiar el puntero en ella. Para crear un verdadero clon, debe ser más creativo y utilizar un método que cree otra copia del objeto original en la memoria.
  • Clonar objetos incorrectamente puede provocar errores muy difíciles de localizar. En el código de producción, tiendo a implementar una suma de verificación para verificar que el objeto se haya clonado correctamente y que no haya sido dañado por otra referencia al mismo. Esta suma de verificación se puede desactivar en modo Release.
  • Encuentro este método bastante útil: a menudo, solo desea clonar partes del objeto, no todo. También es esencial para cualquier caso de uso en el que esté modificando objetos y luego alimentando las copias modificadas en una cola.

Update

Probablemente sea posible usar 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, incluidos todos sus elementos secundarios. El truco consiste en anotar todos los objetos secundarios con un atributo que los haga detectables. Sin embargo, puede perder algunos beneficios de rendimiento.

Update

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

  

He ejecutado mi propia prueba de velocidad usando serializar / deserializar de Neil   método de extensión, Noneted MemberwiseClone de Contango, Alex Burtsev's   método de extensión basado en reflexión y AutoMapper, 1 millón de veces   cada. Serializar-deserializar fue el más lento, con 15.7 segundos. Entonces   llegó AutoMapper, tomando 10.1 segundos. Mucho más rápido fue el   Método basado en la reflexión que tardó 2,4 segundos. De lejos, el más rápido fue   MemberwiseClone anidado, tomando 0.1 segundos. Se reduce a rendimiento   versus la molestia de agregar código a cada clase para clonarlo. Si el rendimiento   No es un problema ir con el método de Alex Burtsev.   & # 8211; 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 los requisitos de ProtoBuf. Desde la página de inicio de ProtoBuf ( http://code.google.com/p/ protobuf-net / wiki / GettingStarted ):

Notas sobre los tipos admitidos:

Clases personalizadas que:

  • Están marcados como contrato de datos
  • Tener un constructor sin parámetros
  • Para Silverlight: son públicos
  • Muchas primitivas comunes, etc.
  • Matrices de dimensiones individuales : T []
  • Lista < T > / IList & Lt; T & Gt;
  • Diccionario < TKey, TValue > / IDictionary & Lt; TKey, TValue & Gt;
  • cualquier tipo que implemente IEnumerable < T > y tiene 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 deberían ser inmutables.

Si su clase cumple con estos requisitos, puede 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 ...

Edición :

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

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 al DetoX83 artículo en el código proyecto.

Quizás 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

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

La documentación de MSDN parece indicar que Clone debería realizar una copia profunda, pero nunca se dice explícitamente:

La interfaz ICloneable contiene un miembro, Clone, que está destinado a admitir la clonación más allá de la proporcionada por MemberWiseClone & # 8230; El método MemberwiseClone crea una copia superficial & # 8230;

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 varias 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<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};
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top