P. ¿Por qué elegiría esta respuesta?
- Elija esta respuesta si desea la velocidad más rápida que .NET es capaz de hacer.
- Ignore esta respuesta si desea un método de clonación realmente sencillo.
En otras palabras, elija otra respuesta, a menos que tenga un cuello de botella en el rendimiento que deba corregirse, y pueda probarlo con un generador de perfiles .
10 veces más rápido que otros métodos
El siguiente método para realizar un clon profundo es:
- 10 veces más rápido que cualquier cosa que implique serialización / deserialización;
- Bastante cerca de la velocidad máxima teórica que .NET es capaz de hacer.
Y el método ...
Para una velocidad máxima, puede usar Nested MemberwiseClone para hacer una copia profunda . Es casi la misma velocidad que copiar una estructura de valores, y es mucho 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 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 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 para 100,000 clones:
- 1.08 segundos para Nested MemberwiseClone en estructuras anidadas
- 4.77 segundos para Nested MemberwiseClone en clases anidadas
- 39.93 segundos para serialización / deserialización
Usar Nested MemberwiseClone en una clase 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 hacer .NET.
Demo 1 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 2 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 3 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 que se utilizó para generar los tiempos anteriores:
// 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 1 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\n", sw.Elapsed, total);
}
{
Console.Write("Demo 2 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\n", sw.Elapsed, total);
}
{
Console.Write("Demo 3 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 ShallowCopy para crear un clon completo. Esto es simple: solo unas pocas líneas en total, consulte el código de demostración anterior.
Tipos de valor frente a tipos de referencias
Tenga en cuenta que cuando se trata de clonar un objeto, hay una gran diferencia entre una " estructura " y una " clase ":
- Si tiene una " estructura ", es un tipo de valor, por lo que puede copiarla y el contenido se clonará (pero solo hará un clon superficial a menos que use las técnicas en esta publicación).
- 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 verdadero clon, debe ser más creativo y utilizar las diferencias entre los tipos de valores y los tipos de referencias, lo que crea otra copia del objeto original en la memoria.
Vea las diferencias entre los tipos de valores y los tipos de referencias .
Sumas de verificación para ayudar en la depuración
- 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 el modo Release.
- Este método me parece bastante útil: a menudo, solo desea clonar partes del objeto, no todo.
Realmente útil para desacoplar muchos hilos de muchos otros hilos
Un excelente caso de uso para este código es alimentar clones de una clase o estructura anidada en una cola, para implementar el patrón productor / consumidor.
- Podemos tener uno (o más) subprocesos que modifiquen una clase que poseen, y luego empujar una copia completa de esta clase a un
ConcurrentQueue
.
- Luego tenemos uno (o más) hilos que extraen copias de estas clases y se ocupan de ellas.
Esto funciona extremadamente bien en la práctica y nos permite desacoplar muchos hilos (los productores) de uno o más hilos (los consumidores).
Y este método también es increíblemente rápido: si usamos estructuras anidadas, es 35 veces más rápido que la serialización / deserialización de clases anidadas, y nos permite aprovechar todos los hilos disponibles en la máquina.
Actualizar
Aparentemente, ExpressMapper es tan rápido, si no más rápido, que la codificación manual como la anterior. Puede que tenga que ver cómo se comparan con un generador de perfiles.