2671 Stimmen

Tiefes Klonen von Objekten

Ich möchte etwas tun wie:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

Und nehmen Sie dann Änderungen am neuen Objekt vor, die sich nicht im ursprünglichen Objekt widerspiegeln.

Ich brauche diese Funktionalität nicht oft, und wenn es nötig war, habe ich ein neues Objekt erstellt und dann jede Eigenschaft einzeln kopiert, aber das hinterlässt bei mir immer das Gefühl, dass es eine bessere oder elegantere Möglichkeit gibt, die Situation zu lösen.

Wie kann ich ein Objekt klonen oder tief kopieren, so dass das geklonte Objekt geändert werden kann, ohne dass sich die Änderungen auf das Originalobjekt auswirken?

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

15voto

Marcell Toth Punkte 2901

Haftungsausschluss: Ich bin der Autor des genannten Pakets.

Ich war überrascht, dass die meisten Antworten auf diese Frage im Jahr 2019 immer noch Serialisierung oder Reflexion verwenden.

Die Serialisierung ist einschränkend (erfordert Attribute, spezifische Konstruktoren usw.) und sehr langsam

BinaryFormatter erfordert die Serializable Attribut, JsonConverter erfordert einen parameterlosen Konstruktor oder Attribute, beide handhaben nur lesbare Felder oder Schnittstellen sehr gut und beide sind 10-30x langsamer als nötig.

Ausdrucksbäume

Sie können stattdessen Folgendes verwenden Ausdrucksbäume o Reflexion.emittieren um den Kloncode nur einmal zu erzeugen, und verwenden Sie dann diesen kompilierten Code anstelle der langsamen Reflexion oder Serialisierung.

Da ich selbst auf das Problem gestoßen bin und keine zufriedenstellende Lösung gefunden habe, habe ich beschlossen, ein Paket zu erstellen, das genau das tut und funktioniert mit jedem Typ und ist fast so schnell wie ein selbst geschriebener Code .

Sie können das Projekt auf GitHub finden: https://github.com/marcelltoth/ObjectCloner

Verwendung

Sie können es über NuGet installieren. Entweder erhalten Sie die ObjectCloner Paket und verwenden Sie es als:

var clone = ObjectCloner.DeepClone(original);

oder wenn es Ihnen nichts ausmacht, Ihren Objekttyp mit Erweiterungen zu verunreinigen, erhalten Sie ObjectCloner.Extensions auch und schreiben:

var clone = original.DeepClone();

Leistung

Ein einfacher Benchmark, bei dem eine Klassenhierarchie geklont wurde, zeigte eine Leistung, die ~3x schneller war als bei der Verwendung von Reflection, ~12x schneller als bei der Newtonsoft.Json-Serialisierung und ~36x schneller als bei der stark empfohlenen BinaryFormatter .

0 Stimmen

Der Grund, warum die Serialisierung auch 2019 noch beliebt ist, liegt darin, dass die Codegenerierung NUR in vertrauenswürdigen Umgebungen funktioniert. Das bedeutet, dass sie in Unity oder iOS nicht funktioniert und wahrscheinlich auch nie funktionieren wird. Codegenerierung ist also nicht portierbar.

0 Stimmen

Ich habe die Version 12.0.3 von NewtonSoft verwendet, meine Klasse hat keinen Parameter-Konstruktor und es funktioniert bei mir

1 Stimmen

Schönes Paket, ich habe es heute in Betrieb genommen. Nur eine Sache, die ich bemerkt habe, der Namespace und der Klassenname sind gleich, also um die statische Methode der Klasse zu verwenden ObjectCloner muss ich trotz der Verwendung der Richtlinie explizit aus dem Namespace kommen, wie zum Beispiel - ObjectCloner.ObjectCloner.DeepClone(someObject) .

14voto

HappyDude Punkte 2530

Im Allgemeinen implementieren Sie die ICloneable-Schnittstelle und implementieren Clone selbst. C#-Objekte haben eine eingebaute MemberwiseClone-Methode, die eine flache Kopie durchführt, die Ihnen bei allen Primitiven helfen kann.

Bei einer tiefen Kopie kann es nicht wissen, wie es das automatisch tun soll.

1 Stimmen

ICloneable hat keine generische Schnittstelle, so dass es nicht empfohlen wird, diese Schnittstelle zu verwenden.

13voto

Daniel Mošmondor Punkte 19346

Ich habe mir das ausgedacht, um eine .NET Manko, dass man List<T> manuell tief kopieren muss.

Ich benutze dies:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

Und an einem anderen Ort:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

Ich habe versucht, mit oneliner zu kommen, die dies tut, aber es ist nicht möglich, aufgrund der Ausbeute nicht innerhalb anonymer Methode Blöcke arbeiten.

Noch besser ist es, einen generischen List<T>-Klon zu verwenden:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

13voto

Contango Punkte 70203

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.

0 Stimmen

Wenn Sie eine Struktur kopieren, erhalten Sie eine flache Kopie, für eine tiefe Kopie benötigen Sie möglicherweise noch eine spezielle Implementierung.

0 Stimmen

@Lasse V. Karlsen. Ja, Sie haben völlig recht, ich habe die Antwort aktualisiert, um dies deutlicher zu machen. Diese Methode kann verwendet werden, um tiefe Kopien von structs zu erstellen y Klassen. Sie können den beigefügten Beispiel-Democode ausführen, um zu zeigen, wie es geht. Er enthält ein Beispiel für das tiefe Klonen einer verschachtelten Struktur und ein weiteres Beispiel für das tiefe Klonen einer verschachtelten Klasse.

11voto

Sean McAvoy Punkte 151

Erstellen Sie eine Erweiterung:

public static T Clone<T>(this T theObject)
{
    string jsonData = JsonConvert.SerializeObject(theObject);
    return JsonConvert.DeserializeObject<T>(jsonData);
}

Und so soll es sein:

NewObject = OldObject.Clone();

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X