D. Perché dovrei scegliere questa risposta?
- Scegli questa risposta se desideri la massima velocità di .NET.
- Ignora questa risposta se desideri un metodo di clonazione davvero molto semplice.
In altre parole, vai con un'altra risposta a meno che tu non abbia un collo di bottiglia delle prestazioni che deve essere riparato e puoi provarlo con un profiler .
10 volte più veloce di altri metodi
Il seguente metodo per eseguire un clone profondo è:
- 10 volte più veloce di tutto ciò che comporta la serializzazione / deserializzazione;
- Abbastanza vicino alla velocità massima teorica di cui è capace .NET.
E il metodo ...
Per la massima velocità, puoi utilizzare Nested MemberwiseClone per eseguire una copia approfondita . È quasi la stessa velocità della copia di una struttura di valore ed è molto più veloce di (a) riflessione o (b) serializzazione (come descritto in altre risposte in questa pagina).
Si noti che se si utilizza Nested MemberwiseClone per una copia profonda , devi implementare manualmente uno ShallowCopy per ogni livello nidificato nella classe e un DeepCopy che chiama tutti i suddetti metodi ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo di seguito.
Ecco l'output del codice che mostra la differenza relativa delle prestazioni per 100.000 cloni:
- 1,08 secondi per Nested MemberwiseClone su strutture nidificate
- 4,77 secondi per Nested MemberwiseClone su classi nidificate
- 39,93 secondi per serializzazione / deserializzazione
Usare Nested MemberwiseClone su una classe quasi alla stessa velocità della copia di una struttura e la copia di una struttura è quasi dannatamente vicino alla velocità massima teorica di cui è in grado di fare .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
Per capire come fare una copia profonda usando MemberwiseCopy, ecco il progetto demo che è stato usato per generare i tempi precedenti:
// 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;
}
}
Quindi, chiama la demo da 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();
}
Ancora una volta, nota che se usi Nested MemberwiseClone per una copia profonda , devi implementare manualmente uno ShallowCopy per ogni livello nidificato nella classe e un DeepCopy che chiama tutti i suddetti metodi ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo sopra.
Tipi di valore e tipi di riferimenti
Si noti che quando si tratta di clonare un oggetto, c'è una grande differenza tra una " struct " e una " class ":
- Se hai una " struct ", è un tipo di valore in modo che tu possa semplicemente copiarlo e il contenuto verrà clonato (ma creerà solo un clone superficiale a meno che tu non usi le tecniche in questo post).
- Se hai una " classe ", è un tipo di riferimento , quindi se la copi, tutto ciò che stai facendo è copiare il puntatore su di essa. Per creare un vero clone, devi essere più creativo e utilizzare le differenze tra tipi di valore e tipi di riferimenti che creano un'altra copia dell'oggetto originale in memoria.
Vedi le differenze tra tipi di valore e tipi di riferimenti .
Checksum per facilitare il debug
- La clonazione errata di oggetti può portare a bug molto difficili da individuare. Nel codice di produzione, tendo a implementare un checksum per verificare che l'oggetto sia stato clonato correttamente e non sia stato danneggiato da un altro riferimento ad esso. Questo checksum può essere disattivato in modalità Rilascio.
- Trovo questo metodo abbastanza utile: spesso vuoi solo clonare parti dell'oggetto, non l'intera cosa.
Davvero utile per disaccoppiare molti thread da molti altri thread
Un eccellente caso d'uso per questo codice è l'invio di cloni di una classe o struttura nidificata in una coda, per implementare il modello produttore / consumatore.
- Possiamo avere uno (o più) thread che modificano una classe di loro proprietà, quindi inserire una copia completa di questa classe in a
ConcurrentQueue
.
- Abbiamo quindi uno (o più) thread che estraggono copie di queste classi e le gestiscono.
In pratica funziona molto bene e ci consente di separare molti thread (i produttori) da uno o più thread (i consumatori).
E questo metodo è anche incredibilmente veloce: se usiamo le strutture nidificate, è 35 volte più veloce della serializzazione / deserializzazione delle classi nidificate e ci consente di sfruttare tutti i thread disponibili sulla macchina.
Aggiornare
Apparentemente, ExpressMapper è più veloce, se non più veloce, della codifica manuale come sopra. Potrei dover vedere come si confrontano con un profiler.