Q. Pourquoi devrais-je choisir cette réponse?
- Choisissez cette réponse si vous voulez que la vitesse la plus rapide dont .NET soit capable.
- Ignorez cette réponse si vous voulez une méthode de clonage vraiment très simple.
En d'autres termes, allez avec une autre réponse, sauf si vous avez un goulot d'étranglement des performances qui doit être corrigé, et vous pouvez le prouver avec un profileur .
10 fois plus rapide que les autres méthodes
La méthode suivante pour effectuer un clone profond est:
- 10 fois plus rapide que tout ce qui implique une sérialisation / désérialisation;
- Assez proche de la vitesse maximale théorique dont .NET est capable.
Et la méthode ...
Pour une vitesse ultime, vous pouvez utiliser Nested MemberwiseClone pour effectuer une copie complète . C'est presque la même vitesse que la copie d'une structure de valeur, et est beaucoup plus rapide que (a) la réflexion ou (b) la sérialisation (comme décrit dans d'autres réponses sur cette page).
Notez que si vous utilisez Nested MemberwiseClone pour une copie complète , vous devez implémenter manuellement un ShallowCopy pour chaque niveau imbriqué de la classe et un DeepCopy qui appelle toutes lesdites méthodes ShallowCopy pour créer un clone complet. C'est simple: seulement quelques lignes au total, voir le code de démonstration ci-dessous.
Voici la sortie du code montrant la différence de performance relative pour 100 000 clones:
- 1,08 secondes pour Nested MemberwiseClone sur les structures imbriquées
- 4,77 secondes pour Nested MemberwiseClone sur les classes imbriquées
- 39,93 secondes pour la sérialisation / désérialisation
L'utilisation de Nested MemberwiseClone sur une classe presque aussi vite que la copie d'une structure, et la copie d'une structure est sacrément proche de la vitesse maximale théorique dont .NET est capable.
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
Pour comprendre comment faire une copie complète à l'aide de MemberwiseCopy, voici le projet de démonstration qui a été utilisé pour générer les temps ci-dessus:
// 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;
}
}
Ensuite, appelez la démo depuis 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();
}
Encore une fois, notez que si vous utilisez Nested MemberwiseClone pour une copie complète , vous devez implémenter manuellement un ShallowCopy pour chaque niveau imbriqué de la classe et un DeepCopy qui appelle toutes lesdites méthodes ShallowCopy pour créer un clone complet. C'est simple: seulement quelques lignes au total, voir le code de démonstration ci-dessus.
Types de valeurs vs types de références
Notez que lorsqu'il s'agit de cloner un objet, il y a une grande différence entre une " struct " et une " classe ":
- Si vous avez un " struct ", c'est un type de valeur , vous pouvez donc simplement le copier, et le contenu sera cloné (mais il ne fera qu'un clone superficiel à moins que vous n'utilisiez les techniques de cet article).
- Si vous avez une " classe ", c'est un type de référence , donc si vous la copiez, tout ce que vous faites est de copier le pointeur dessus. Pour créer un véritable clone, vous devez être plus créatif et utiliser des différences entre les types de valeur et les types de référence, ce qui crée une autre copie de l'objet d'origine en mémoire.
Voir les différences entre les types de valeurs et les types de références .
Sommes de contrôle pour faciliter le débogage
- Un clonage incorrect d'objets peut entraîner des bogues très difficiles à identifier. Dans le code de production, j'ai tendance à implémenter une somme de contrôle pour vérifier que l'objet a été cloné correctement et qu'il n'a pas été corrompu par une autre référence. Cette somme de contrôle peut être désactivée en mode Release.
- Je trouve cette méthode assez utile: souvent, vous ne voulez que cloner des parties de l'objet, pas la totalité.
Vraiment utile pour découpler de nombreux threads de nombreux autres threads
Un excellent cas d'utilisation de ce code consiste à alimenter des clones d'une classe ou d'une structure imbriquée dans une file d'attente, pour implémenter le modèle producteur / consommateur.
- Nous pouvons avoir un (ou plusieurs) threads modifiant une classe dont ils sont propriétaires, puis en poussant une copie complète de cette classe dans un
ConcurrentQueue
.
- Nous avons ensuite un (ou plusieurs) threads tirant des copies de ces classes et les traitant.
Cela fonctionne extrêmement bien dans la pratique et nous permet de découpler de nombreux threads (les producteurs) d'un ou plusieurs threads (les consommateurs).
Et cette méthode est également incroyablement rapide: si nous utilisons des structures imbriquées, elle est 35 fois plus rapide que la sérialisation / désérialisation de classes imbriquées, et nous permet de tirer parti de tous les threads disponibles sur la machine.
Mise à jour
Apparemment, ExpressMapper est aussi rapide, sinon plus rapide, que le codage manuel tel que ci-dessus. Je devrais peut-être voir comment ils se comparent à un profileur.