641 votos

¿Cómo se hace una copia en profundidad un objeto en Net (C # en concreto)?

Quiero una copia fiel de profundidad. En Java, esto era fácil, pero ¿cómo hacerlo en C #?

690voto

Kilhoffer Puntos 13430

He visto un par de enfoques diferentes para esto, pero yo uso un método de utilidad genérica 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 estar marcada como [Serializable] para que esto funcione.
  • El archivo de origen debe incluir el siguiente código:

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

380voto

Alex Burtsev Puntos 4251

Escribí método de extensión copia objeto profundo, basado en "MemberwiseClone" recursiva, es rápido (3 veces más rápido que BinaryFormatter), funciona con cualquier objeto, no es necesario constructor predeterminado o atributos serializables.

https://raw.github.com/Burtsev-Alexey/net-objeto-deep-copy/master/ObjectExtensions.cs

174voto

Neil Puntos 1859

Sobre la base de 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 se extiende a cualquier clase que se ha marcado como [Serializable] con un método DeepClone

 MyClass copy = obj.DeepClone();
 

56voto

Contango Puntos 7976

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

Tenga en cuenta que si usted utiliza Anidadas MemberwiseClone para una copia profunda, usted tiene que manualmente implementar un ShallowCopy para cada nivel anidado en la clase, y una DeepCopy que llama a todos, dijo ShallowCopy métodos para crear un clon completo. Esto es simple: sólo un par de líneas en total, vea el código de demostración a continuación.

Aquí está el resultado del código que muestra el rendimiento relativo de diferencia (4.77 segundos para la profundidad de anidado MemberwiseCopy vs. 39.93 segundos para la Serialización). El uso de anidado MemberwiseCopy es casi tan rápido como la copia de una estructura, y de la copia de una estructura es bastante maldito cerca de la velocidad máxima teórica .NET es capaz de hacer.

    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 en profundidad el uso de 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 de las principales:

    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 usted utiliza Anidadas MemberwiseClone para una copia profunda, usted tiene que manualmente implementar un ShallowCopy para cada nivel anidado en la clase, y una DeepCopy que llama a todos, dijo ShallowCopy métodos para crear un clon completo. Esto es simple: sólo un par de líneas en total, vea la demo código de arriba.

Tenga en cuenta que cuando se trata de la clonación de un objeto, existe una gran diferencia entre "estructura" y una "clase":

  • Si usted tiene una "estructura", es un tipo de valor, de modo que usted puede copiar, y el contenido será clonado.
  • Si usted tiene una "clase", es un tipo de referencia, por lo que si usted la copia, todo lo que están haciendo es copiar el puntero. Para crear un verdadero clon, tiene que ser más creativos, y el uso de un método que crea otra copia del objeto original en la memoria.
  • Clonación de objetos de forma incorrecta puede conducir a muy difícil-a-pin-de errores. En el código de producción, que tienden a implementar una suma de verificación para verificar que el objeto ha sido clonado correctamente, y no ha sido dañado por otro de referencia. Esta suma de comprobación puede ser apagado en modo de Lanzamiento.
  • Me parece que este método es bastante útil: a menudo, usted sólo desea clonar partes de los objetos, no el de toda la cosa. Es también esencial para cualquier caso de uso donde se están modificando los objetos, entonces la alimentación de la modificación de copias en una cola.

Actualización

Probablemente es posible utilizar la reflexión de forma recursiva a pie a través del objeto gráfico 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. Usted podría perder algunos de los beneficios en el rendimiento, sin embargo.

21voto

Kurt Richardson Puntos 51

Yo creo que el BinaryFormatter enfoque es relativamente lento (que llegó como una sorpresa para mí!). Usted puede ser capaz de utilizar ProtoBuf .Net para algunos objetos si cumplen con los requisitos de ProtoBuf. Desde el ProtoBuf página de introducción (http://code.google.com/p/protobuf-net/wiki/GettingStarted):

Notas sobre tipos de soporte admitidos:

clases personalizadas:

  • están marcados como los datos del contrato
  • tiene un constructor sin parámetros
  • para Silverlight: público
  • muchas de las primitivas etc
  • solo matrices de dimensión: T[]
  • Lista / IList
  • Diccionario / IDictionary
  • cualquier tipo que implementa IEnumerable y tiene un Add(T) para el método de

El código asume que los tipos mutables alrededor de los miembros electos. En consecuencia, la costumbre estructuras no son compatibles, ya que deben ser inmutables.

Si la clase cumple con estos requisitos, puedes intentar:

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 MUY rápido, de hecho...

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X