Q. 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 suchen.
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 nachweisen .
10x schneller als andere Methoden
Die folgende Methode zur Durchführung eines tiefen Klons ist:
- 10x schneller als alles, was Serialisierung/Deserialisierung beinhaltet;
- Das ist verdammt nah an der theoretischen Höchstgeschwindigkeit, zu der .NET fähig ist.
Und die Methode ...
Für ultimative Geschwindigkeit können Sie Folgendes verwenden Verschachteltes MemberwiseClone zur Erstellung einer tiefen Kopie . Es ist fast genauso schnell wie das Kopieren eines Wertes struct, und ist viel schneller als (a) Reflexion oder (b) Serialisierung (wie in anderen Antworten auf dieser Seite beschrieben).
Beachten Sie, dass wenn Sie verwenden Verschachtelter MemberwiseClone für eine tiefe Kopie müssen Sie manuell eine ShallowCopy für jede verschachtelte Ebene in der Klasse und eine DeepCopy implementieren, die alle genannten ShallowCopy-Methoden aufruft, um einen vollständigen Klon zu erstellen. Das ist ganz einfach: insgesamt nur ein paar Zeilen, siehe den 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 bei verschachtelten Strukturen
- 4,77 Sekunden für Nested MemberwiseClone bei verschachtelten Klassen
- 39,93 Sekunden für Serialisierung/Desialisierung
Die Verwendung von Nested MemberwiseClone auf einer Klasse ist fast so schnell wie das Kopieren einer Struktur, und das Kopieren einer Struktur ist verdammt nah an der theoretischen Höchstgeschwindigkeit, die .NET erreichen kann.
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 man eine tiefe Kopie mit MemberwiseCopy durchführt, sehen Sie hier das Demoprojekt, das verwendet wurde, um die obigen Zeiten zu erzeugen:
// 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 aus 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();
}
Auch hier ist zu beachten, dass wenn Sie verwenden Verschachtelter MemberwiseClone für eine tiefe Kopie müssen Sie manuell eine ShallowCopy für jede verschachtelte Ebene in der Klasse und eine DeepCopy implementieren, die alle genannten ShallowCopy-Methoden aufruft, um einen vollständigen Klon zu erstellen. Das ist ganz einfach: insgesamt nur ein paar Zeilen, siehe den Demo-Code oben.
Werttypen vs. Referenztypen
Beachten Sie, dass es beim Klonen eines Objekts einen großen Unterschied zwischen einem " Struktur " und ein " Klasse ":
- Wenn Sie einen " " haben Struktur ", es ist ein Werttyp Sie können es also einfach kopieren, und der Inhalt wird geklont (allerdings wird nur ein oberflächlicher Klon erstellt, es sei denn, Sie wenden die Techniken in diesem Beitrag an).
- Wenn Sie einen " " haben Klasse ", es ist ein Referenztyp Wenn Sie es also kopieren, kopieren Sie nur den Zeiger darauf. Um einen echten Klon zu erstellen, müssen Sie kreativer sein und Folgendes verwenden Unterschiede zwischen Werttypen und Referenztypen die eine weitere Kopie des ursprünglichen Objekts im Speicher anlegt.
Voir Unterschiede zwischen Werttypen und Referenztypen .
Prüfsummen zur Unterstützung bei der Fehlersuche
- Falsches Klonen von Objekten kann zu sehr schwer auffindbaren Fehlern führen. Im Produktionscode neige ich dazu, eine Prüfsumme zu implementieren, um sicherzustellen, dass das Objekt ordnungsgemäß geklont wurde und nicht durch einen anderen Verweis auf das Objekt beschädigt wurde. Diese Prüfsumme kann im Release-Modus ausgeschaltet werden.
- Ich finde diese Methode sehr nützlich: Oft möchte man nur Teile des Objekts klonen, nicht das ganze Objekt.
Sehr nützlich für die Entkopplung vieler Threads von vielen anderen Threads
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
ConcurrentQueue
.
- Dann gibt es einen (oder mehrere) Threads, die Kopien dieser Klassen herausziehen und mit ihnen umgehen.
Dies funktioniert in der Praxis sehr gut und ermöglicht es uns, viele Threads (die Produzenten) von einem oder mehreren Threads (den Konsumenten) zu entkoppeln.
Und diese Methode ist auch verdammt schnell: Wenn wir verschachtelte Strukturen verwenden, ist sie 35x schneller als das Serialisieren/Deserialisieren von verschachtelten Klassen und erlaubt uns, alle auf dem Rechner verfügbaren Threads zu nutzen.
Update
Offensichtlich ist ExpressMapper genauso schnell, wenn nicht sogar schneller, als eine manuelle Programmierung wie die oben beschriebene. Ich könnte sehen, wie sie mit einem Profiler vergleichen müssen.
112 Stimmen
Kann nützlich sein: "Warum ist das Kopieren eines Objekts eine schreckliche Sache?" agiledeveloper.com/articles/cloning072002.htm
2 Stimmen
stackoverflow.com/questions/8025890/ Eine andere Lösung...
28 Stimmen
Werfen Sie einen Blick auf AutoMapper
4 Stimmen
Deine Lösung ist viel komplexer, ich habe mich beim Lesen verlaufen... hehehe. Ich verwende eine DeepClone-Schnittstelle. public interface IDeepCloneable<T> { T DeepClone(); }
0 Stimmen
@Pedro77: Ein Anliegen, das ich habe
IDeepCloneable
ist, dass nicht alle Sammlungen von Verweisen auf Dinge, die tief geklont werden können, geklont werden sollten; das richtige Verhalten beim Klonen einerList<T>
hängt nicht nur vonT
sondern auch nach dem Zweck der Listen. Wenn keines der Elemente in den Listen jemals etwas ausgesetzt sein wird, das sie verändern könnte, wäre es besser, die Verweise direkt zu kopieren, selbst wenn die Elemente innerhalb der Listen geklont werden könnten.0 Stimmen
Diese Frage wird auch hier beantwortet stackoverflow.com/q/129389/235715
4 Stimmen
@Pedro77 -- Interessanterweise heißt es in dem Artikel am Ende, dass man eine
clone
Methode auf die Klasse anwenden und dann einen internen, privaten Konstruktor aufrufen, der anthis
. Kopieren ist also schrecklich [sic], aber sorgfältiges Kopieren (und der Artikel ist definitiv lesenswert) ist es nicht. ;^)0 Stimmen
Wenn Sie dies benötigen, haben Sie vielleicht eine falsche Implementierung. Und wenn Sie Dependency Injection verwenden, macht es überhaupt keinen Sinn.
0 Stimmen
Letztendlich sind diese Frage und alle Antworten ungefähr so nützlich wie "Wie programmiere ich eine Klasse?" Es gibt viele Antworten, aber es gibt keine einzige richtige Antwort, trotz Abstimmungen. Das heißt NICHT, dass keine Antwort nützlich ist oder dass die Frage nicht sinnvoll ist, aber man sollte sich vor polarisierenden Antworten hüten. Das größte Defizit ist hier die Betonung der Bereitstellung einer detaillierten Dokumentation und der Verantwortung des Benutzers/Implementierers einer Klasse für das Verständnis der Details jeder Kopieroperation.
0 Stimmen
Warum nicht einfach eine neue Instanz davon besorgen? Oder wenn Sie ein Objekt kopieren wollen, das Sie geändert haben, anstatt es nur zu instanziieren, können Sie auch eine Methode erstellen, die all das tut, und diese Methode einfach zweimal aufrufen.
0 Stimmen
Rufen Sie die MemberwiseClone-Methode auf, um eine oberflächliche Kopie eines Objekts zu erstellen, und weisen Sie dann allen Eigenschaften oder Feldern, deren Werte Referenztypen sind, neue Objekte zu, deren Werte mit denen des Originalobjekts übereinstimmen. Die DeepCopy-Methode im Beispiel veranschaulicht diesen Ansatz. msdn.microsoft.com/de-us/library/
0 Stimmen
Prüfen Sie dies Antwort : stackoverflow.com/a/52097307/4707576 über: Klonen von Objekten ohne Serialisierung