F. Warum sollte ich diese Antwort wählen?
- Wählen Sie diese Antwort, wenn Sie die schnellste Geschwindigkeit wünschen, zu der .NET fähig ist.
- Ignorieren Sie diese Antwort, wenn Sie eine wirklich, wirklich einfache Methode zum Klonen wünschen.
Mit anderen Worten, wählen Sie eine andere Antwort, es sei denn, Sie haben einen Leistungsengpass, der behoben werden muss, und Sie können dies mit einem Profiler beweisen .
10x schneller als andere Methoden
Die folgende Methode zum Ausführen eines tiefen Klons ist:
- 10x schneller als alles, was Serialisierung / Deserialisierung beinhaltet;
- Ziemlich nahe an der theoretischen Höchstgeschwindigkeit, zu der .NET fähig ist.
Und die Methode ...
Für ultimative Geschwindigkeit können Sie Nested MemberwiseClone verwenden, um eine tiefe Kopie zu erstellen . Es ist fast genauso schnell wie das Kopieren einer Wertestruktur und viel schneller als (a) Reflexion oder (b) Serialisierung (wie in anderen Antworten auf dieser Seite beschrieben).
Beachten Sie, dass Sie , wenn Sie Nested MemberwiseClone für eine Deep Copy verwenden , manuell eine ShallowCopy für jede verschachtelte Ebene in der Klasse und eine DeepCopy implementieren müssen, die alle genannten ShallowCopy-Methoden aufruft, um einen vollständigen Klon zu erstellen. Das ist ganz einfach: Insgesamt nur wenige Zeilen, siehe Demo-Code unten.
Hier ist die Ausgabe des Codes, die den relativen Leistungsunterschied für 100.000 Klone zeigt:
- 1,08 Sekunden für Nested MemberwiseClone für verschachtelte Strukturen
- 4,77 Sekunden für Nested MemberwiseClone für verschachtelte Klassen
- 39,93 Sekunden für Serialisierung / Deserialisierung
Die Verwendung von Nested MemberwiseClone für eine Klasse ist fast so schnell wie das Kopieren einer Struktur, und das Kopieren einer Struktur kommt der theoretischen Höchstgeschwindigkeit, zu der .NET fähig ist, verdammt nahe.
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
Um zu verstehen, wie mit MemberwiseCopy eine tiefe Kopie erstellt wird, finden Sie hier das Demo-Projekt, mit dem die oben genannten Zeiten generiert wurden:
// 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;
}
}
Rufen Sie dann die Demo von main auf:
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();
}
Beachten Sie erneut, dass Sie , wenn Sie Nested MemberwiseClone für eine tiefe Kopie verwenden , manuell eine ShallowCopy für jede verschachtelte Ebene in der Klasse und eine DeepCopy implementieren müssen, die alle genannten ShallowCopy-Methoden aufruft, um einen vollständigen Klon zu erstellen. Das ist ganz einfach: Insgesamt nur wenige Zeilen, siehe Demo-Code oben.
Werttypen vs. Referenztypen
Beachten Sie, dass es beim Klonen eines Objekts einen großen Unterschied zwischen einer " Struktur " und einer " Klasse " gibt:
- Wenn Sie ein „haben struct “, es ist ein Werttyp , so dass Sie es einfach kopieren können, und der Inhalt wird geklont werden (aber es wird nur eine flache Klon machen , wenn Sie die Techniken in diesem Beitrag nicht verwenden).
- Wenn Sie eine " Klasse " haben, handelt es sich um einen Referenztyp. Wenn Sie sie also kopieren, kopieren Sie lediglich den Zeiger darauf. Um einen echten Klon zu erstellen, müssen Sie kreativer sein und Unterschiede zwischen Werttypen und Referenztypen verwenden , um eine weitere Kopie des ursprünglichen Objekts im Speicher zu erstellen .
Siehe Unterschiede zwischen Werttypen und Referenzen - Typen .
Prüfsummen zur Unterstützung des Debuggens
- Falsches Klonen von Objekten kann zu sehr schwer zu behebenden Fehlern führen. Im Produktionscode tendiere ich dazu, eine Prüfsumme zu implementieren, um zu überprüfen, ob das Objekt ordnungsgemäß geklont wurde und nicht durch einen anderen Verweis darauf beschädigt wurde. Diese Prüfsumme kann im Release-Modus ausgeschaltet werden.
- Ich finde diese Methode sehr nützlich: Oft möchten Sie nur Teile des Objekts klonen, nicht das Ganze.
Wirklich nützlich, um viele Threads von vielen anderen Threads zu entkoppeln
Ein ausgezeichneter Anwendungsfall für diesen Code ist das Einspeisen von Klonen einer verschachtelten Klasse oder Struktur in eine Warteschlange, um das Producer / Consumer-Muster zu implementieren.
- Wir können einen (oder mehrere) Threads haben, die eine Klasse ändern, die sie besitzen, und dann eine vollständige Kopie dieser Klasse in eine Klasse verschieben
ConcurrentQueue
.
- Wir haben dann einen (oder mehrere) Threads, die Kopien dieser Klassen herausziehen und sich mit ihnen befassen.
Dies funktioniert in der Praxis sehr gut und ermöglicht es uns, viele Threads (die Produzenten) von einem oder mehreren Threads (den Verbrauchern) zu entkoppeln.
Und diese Methode ist auch unglaublich schnell: Wenn wir verschachtelte Strukturen verwenden, ist sie 35-mal schneller als das Serialisieren / Deserialisieren verschachtelter Klassen und ermöglicht es uns, alle auf dem Computer verfügbaren Threads zu nutzen.
Aktualisieren
Anscheinend ist ExpressMapper genauso schnell, wenn nicht sogar schneller als die Handcodierung wie oben. Ich muss vielleicht sehen, wie sie sich mit einem Profiler vergleichen.