Tiefes Klonen von Objekten

2226

Ich möchte so etwas tun wie:

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

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

Ich brauche diese Funktionalität nicht oft. Wenn es also nötig war, habe ich ein neues Objekt erstellt und dann jede Eigenschaft einzeln kopiert, aber ich habe immer das Gefühl, dass es eine bessere oder elegantere Art der Handhabung gibt die Situation.

Wie kann ich ein Objekt klonen oder tief kopieren, damit das geklonte Objekt geändert werden kann, ohne dass Änderungen im Originalobjekt berücksichtigt werden?

NakedBrunch
quelle
81
Kann nützlich sein: "Warum ist das Kopieren eines Objekts eine schreckliche Sache?" agiledeveloper.com/articles/cloning072002.htm
Pedro77
stackoverflow.com/questions/8025890/… Eine andere Lösung ...
Felix K.
18
Sie sollten einen Blick auf AutoMapper
Daniel Little
3
Ihre Lösung ist weitaus komplexer, ich habe mich beim Lesen verlaufen ... hehehe. Ich verwende eine DeepClone-Schnittstelle. öffentliche Schnittstelle IDeepCloneable <T> {T DeepClone (); }
Pedro77
1
@ Pedro77 - Interessanterweise heißt es in diesem Artikel, eine cloneMethode für die Klasse zu erstellen , und dann einen internen, privaten Konstruktor aufrufen zu lassen, der übergeben wird this. Das Kopieren ist also turrible, aber das sorgfältige Kopieren (und der Artikel ist definitiv lesenswert) nicht. ; ^)
Ruffin

Antworten:

1715

Während die Standardpraxis darin besteht, die ICloneableSchnittstelle zu implementieren ( hier beschrieben , damit ich nicht wieder erbrechen kann), ist hier ein netter Deep-Clone-Objektkopierer, den ich vor einiger Zeit bei The Code Project gefunden und in unsere Inhalte aufgenommen habe.

Wie an anderer Stelle erwähnt, müssen Ihre Objekte serialisierbar sein.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", nameof(source));
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }
}

Die Idee ist, dass es Ihr Objekt serialisiert und es dann in ein neues Objekt deserialisiert. Der Vorteil ist, dass Sie sich nicht darum kümmern müssen, alles zu klonen, wenn ein Objekt zu komplex wird.

Und mit der Verwendung von Erweiterungsmethoden (auch aus der ursprünglich referenzierten Quelle):

Wenn Sie die neuen Erweiterungsmethoden von C # 3.0 bevorzugen , ändern Sie die Methode so, dass sie die folgende Signatur hat:

public static T Clone<T>(this T source)
{
   //...
}

Jetzt wird der Methodenaufruf einfach objectBeingCloned.Clone();.

BEARBEITEN (10. Januar 2015) Ich dachte, ich würde dies noch einmal überprüfen, um zu erwähnen, dass ich kürzlich damit begonnen habe, (Newtonsoft) Json zu verwenden. Es sollte leichter sein und den Overhead von [serialisierbaren] Tags vermeiden. ( NB @atconway hat in den Kommentaren darauf hingewiesen, dass private Mitglieder nicht mit der JSON-Methode geklont werden.)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
Johnc
quelle
24
stackoverflow.com/questions/78536/cloning-objects-in-c/… hat einen Link zum obigen Code [und verweist auf zwei andere solche Implementierungen, von denen eine in meinem Kontext besser geeignet ist]
Ruben Bartelink
102
Die Serialisierung / Deserialisierung ist mit einem erheblichen Aufwand verbunden, der nicht erforderlich ist. Siehe die ICloneable-Schnittstelle und die .MemberWise () -Klonmethoden in C #.
3Dave
18
@ David, zugegeben, aber wenn die Objekte leicht sind und die Leistung bei der Verwendung nicht zu hoch für Ihre Anforderungen ist, ist dies ein nützlicher Tipp. Ich gebe zu, dass ich es mit großen Datenmengen in einer Schleife nicht intensiv genutzt habe, aber ich habe noch nie ein einziges Leistungsproblem gesehen.
Johnc
16
@Amir: eigentlich nein: typeof(T).IsSerializableist auch wahr, wenn der Typ mit dem [Serializable]Attribut markiert wurde . Die ISerializableSchnittstelle muss nicht implementiert werden.
Daniel Gehriger
11
Ich dachte nur, ich würde erwähnen, dass diese Methode zwar nützlich ist und ich sie schon oft selbst verwendet habe, aber überhaupt nicht mit Medium Trust kompatibel ist. Achten Sie also darauf, ob Sie Code schreiben, der Kompatibilität benötigt. BinaryFormatter greift auf private Felder zu und kann daher nicht im Standardberechtigungssatz für Umgebungen mit teilweiser Vertrauenswürdigkeit arbeiten. Sie könnten einen anderen Serializer ausprobieren, aber stellen Sie sicher, dass Ihr Anrufer weiß, dass der Klon möglicherweise nicht perfekt ist, wenn das eingehende Objekt auf privaten Feldern basiert.
Alex Norcliffe
298

Ich wollte einen Kloner für sehr einfache Objekte mit meist primitiven und Listen. Wenn Ihr Objekt standardmäßig JSON-serialisierbar ist, reicht diese Methode aus. Dies erfordert keine Änderung oder Implementierung von Schnittstellen in der geklonten Klasse, sondern nur einen JSON-Serializer wie JSON.NET.

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}

Sie können diese Erweiterungsmethode auch verwenden

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}
Craastad
quelle
13
Die Lösung ist sogar noch schneller als die BinaryFormatter-Lösung .NET Serialization Performance Comparison
esskar
3
Danke dafür. Mit dem BSON-Serializer, der mit dem MongoDB-Treiber für C # geliefert wird, konnte ich im Wesentlichen dasselbe tun.
Mark Ewer
3
Dies ist der beste Weg für mich, aber ich benutze, Newtonsoft.Json.JsonConvertaber es ist der gleiche
Pierre
1
Damit dies funktioniert, muss das zu
klonende
2
Ich denke, dies ist die beste Lösung, da die Implementierung auf die meisten Programmiersprachen angewendet werden kann.
mr5
178

Der Grund, ICloneable nicht zu verwenden, liegt nicht darin, dass es keine generische Schnittstelle gibt. Der Grund, es nicht zu benutzen, ist, weil es vage ist . Es ist nicht klar, ob Sie eine flache oder eine tiefe Kopie erhalten. Das liegt beim Implementierer.

Ja, MemberwiseClonemacht eine flache Kopie, aber das Gegenteil von MemberwiseCloneist nicht Clone; es wäre vielleicht das DeepClone, was es nicht gibt. Wenn Sie ein Objekt über seine ICloneable-Schnittstelle verwenden, können Sie nicht wissen, welche Art des Klonens das zugrunde liegende Objekt ausführt. (Und XML-Kommentare machen es nicht klar, da Sie eher die Schnittstellenkommentare als die Kommentare zur Klonmethode des Objekts erhalten.)

Normalerweise mache ich einfach eine CopyMethode, die genau das macht, was ich will.

Ryan Lundy
quelle
Mir ist nicht klar, warum ICloneable als vage angesehen wird. Bei einem Typ wie Dictionary (Of T, U) würde ich erwarten, dass ICloneable.Clone alle erforderlichen Ebenen für tiefes und flaches Kopieren ausführt, damit das neue Wörterbuch ein unabhängiges Wörterbuch ist, das dieselben T's und U's enthält (Strukturinhalte, und / oder Objektreferenzen) als Original. Wo ist die Mehrdeutigkeit? Ein generisches ICloneable (Of T), das ISelf (Of T) geerbt hat und eine "Self" -Methode enthielt, wäre zwar viel besser, aber ich sehe keine Mehrdeutigkeit beim Klonen von tiefen und flachen Klonen.
Supercat
31
Ihr Beispiel veranschaulicht das Problem. Angenommen, Sie haben ein Wörterbuch <Zeichenfolge, Kunde>. Sollte das geklonte Wörterbuch dieselben Kundenobjekte wie das Original oder Kopien dieser Kundenobjekte haben? Für beide gibt es vernünftige Anwendungsfälle. ICloneable macht jedoch nicht klar, welches Sie erhalten. Deshalb ist es nicht nützlich.
Ryan Lundy
@Kyralessa Der Microsoft MSDN-Artikel beschreibt genau dieses Problem, nicht zu wissen, ob Sie eine tiefe oder flache Kopie anfordern.
Crush
Die Antwort aus dem Duplikat stackoverflow.com/questions/129389/… beschreibt die Kopiererweiterung , basierend auf rekursivem MembershipClone
Michael Freidgeim
123

Nachdem ich viel über viele der hier verlinkten Optionen und mögliche Lösungen für dieses Problem gelesen habe, glaube ich, dass alle Optionen unter Ian Ps Link ziemlich gut zusammengefasst sind (alle anderen Optionen sind Variationen davon) und die beste Lösung von bereitgestellt wird Pedro77 's Link zu den Fragenkommentaren.

Also kopiere ich einfach relevante Teile dieser 2 Referenzen hier. Auf diese Weise können wir haben:

Das Beste zum Klonen von Objekten in Cis!

In erster Linie sind dies alle unsere Optionen:

Der Artikel Fast Deep Copy nach Expressionsbäumen enthält auch einen Leistungsvergleich zum Klonen nach Serialisierungs-, Reflexions- und Expressionsbäumen.

Warum ich ICloneable wähle (dh manuell)

Herr Venkat Subramaniam (redundanter Link hier) erklärt ausführlich, warum .

Alle seine Artikel drehen sich um ein Beispiel, das in den meisten Fällen mit drei Objekten anwendbar sein soll: Person , Gehirn und Stadt . Wir wollen eine Person klonen, die ihr eigenes Gehirn hat, aber dieselbe Stadt. Sie können sich entweder alle Probleme vorstellen, die eine der anderen oben genannten Methoden mit sich bringen kann, oder den Artikel lesen.

Dies ist meine leicht modifizierte Version seiner Schlussfolgerung:

Das Kopieren eines Objekts durch Angabe Newgefolgt vom Klassennamen führt häufig zu Code, der nicht erweiterbar ist. Die Verwendung von Klonen, die Anwendung von Prototypmustern, ist ein besserer Weg, um dies zu erreichen. Die Verwendung von Klonen, wie sie in C # (und Java) bereitgestellt werden, kann jedoch ebenfalls problematisch sein. Es ist besser, einen geschützten (nicht öffentlichen) Kopierkonstruktor bereitzustellen und diesen über die Klonmethode aufzurufen. Dies gibt uns die Möglichkeit, die Aufgabe des Erstellens eines Objekts an eine Instanz einer Klasse selbst zu delegieren, wodurch Erweiterbarkeit bereitgestellt und die Objekte mithilfe des geschützten Kopierkonstruktors sicher erstellt werden.

Hoffentlich kann diese Implementierung die Dinge klar machen:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    
}

Betrachten Sie nun eine Klasse, die von Person abgeleitet ist.

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

Sie können versuchen, den folgenden Code auszuführen:

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

Die erzeugte Ausgabe ist:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

Beachten Sie, dass der hier implementierte Klon, wenn wir die Anzahl der Objekte zählen, die Anzahl der Objekte korrekt zählt.

Cregox
quelle
6
MS empfiehlt, nicht ICloneablefür öffentliche Mitglieder zu verwenden. "Da Aufrufer von Clone nicht von der Methode abhängig sein können, die einen vorhersehbaren Klonvorgang ausführt, empfehlen wir, ICloneable nicht in öffentlichen APIs zu implementieren." msdn.microsoft.com/en-us/library/… Basierend auf der Erklärung von Venkat Subramaniam in Ihrem verlinkten Artikel halte ich es jedoch für sinnvoll, in dieser Situation zu verwenden, solange die Schöpfer der ICloneable-Objekte eine Tiefe haben Verständnis, welche Eigenschaften tief gegen flache Kopien sein sollten (dh tiefe Kopie Gehirn, flache Kopie Stadt)
BateTech
Zunächst einmal bin ich kein Experte für dieses Thema (öffentliche APIs). Ich denke einmal, dass diese MS-Bemerkung sehr viel Sinn macht. Und ich denke nicht, dass es sicher ist anzunehmen, dass die Benutzer dieser API ein so tiefes Verständnis haben werden. Daher ist es nur dann sinnvoll, es auf einer öffentlichen API zu implementieren, wenn es für jeden, der es verwenden wird, wirklich keine Rolle spielt. Ich denke , eine Art UML, die sehr explizit die Unterscheidung für jede Eigenschaft macht, könnte helfen. Aber ich würde gerne von jemandem mit mehr Erfahrung hören. : P
Cregox
Sie können den CGbR-Klongenerator verwenden und ein ähnliches Ergebnis erzielen, ohne den Code manuell zu schreiben.
Toxantron
Die Implementierung einer Zwischensprache ist nützlich
Michael Freidgeim
Es gibt kein Finale in C #
Konrad
84

Ich ziehe einen Kopierkonstruktor einem Klon vor. Die Absicht ist klarer.

Nick
quelle
5
.Net hat keine Kopierkonstruktoren.
Pop Catalin
48
Sicher: neues MyObject (objToCloneFrom) Deklarieren Sie einfach einen ctor, der das zu klonende Objekt als Parameter verwendet.
Nick
30
Es ist nicht dasselbe. Sie müssen es manuell zu jeder Klasse hinzufügen, und Sie wissen nicht einmal, ob Sie eine tiefe Kopie garantieren.
Dave Van den Eynde
15
+1 für Kopie ctor. Sie müssen auch für jeden Objekttyp manuell eine clone () -Funktion schreiben, und viel Glück damit, wenn Ihre Klassenhierarchie einige Ebenen tiefer wird.
Andrew Grant
3
Mit Kopierkonstruktoren verlieren Sie jedoch die Hierarchie. agiledeveloper.com/articles/cloning072002.htm
Will
42

Einfache Erweiterungsmethode zum Kopieren aller öffentlichen Eigenschaften. Funktioniert für alle Objekte und erfordert keine Klasse [Serializable]. Kann für andere Zugriffsebenen erweitert werden.

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}
Konstantin Salavatov
quelle
15
Dies ist leider fehlerhaft. Dies entspricht dem Aufruf von objectOne.MyProperty = objectTwo.MyProperty (dh es wird nur die Referenz kopiert). Die Werte der Eigenschaften werden nicht geklont.
Alex Norcliffe
1
an Alex Norcliffe: Der Autor der Frage fragte nach "Kopieren jeder Eigenschaft" anstatt nach Klonen. In den meisten Fällen ist keine genaue Vervielfältigung von Eigenschaften erforderlich.
Konstantin Salavatov
1
Ich denke über diese Methode nach, aber mit Rekursion. Wenn der Wert einer Eigenschaft eine Referenz ist, erstellen Sie ein neues Objekt und rufen Sie CopyTo erneut auf. Ich sehe nur ein Problem, dass alle verwendeten Klassen einen Konstruktor ohne Parameter haben müssen. Hat das schon jemand versucht? Ich frage mich auch, ob dies tatsächlich mit Eigenschaften funktioniert, die .net-Klassen wie DataRow und DataTable enthalten.
Koryu
33

Ich habe gerade ein CloneExtensionsBibliotheksprojekt erstellt . Es führt ein schnelles, tiefes Klonen mit einfachen Zuweisungsoperationen durch, die durch die Kompilierung des Expression Tree-Laufzeitcodes generiert werden.

Wie benutzt man es?

Anstatt eigene Methoden Cloneoder CopyMethoden mit einem Ton von Zuweisungen zwischen Feldern und Eigenschaften zu schreiben , muss das Programm dies mithilfe des Ausdrucksbaums selbst tun. GetClone<T>()Mit der als Erweiterungsmethode gekennzeichneten Methode können Sie sie einfach auf Ihrer Instanz aufrufen:

var newInstance = source.GetClone();

Sie können wählen , was kopiert werden soll , sourceum newInstancemit CloningFlagsEnum:

var newInstance 
    = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);

Was kann geklont werden?

  • Primitive (int, uint, byte, double, char usw.), bekannte unveränderliche Typen (DateTime, TimeSpan, String) und Delegaten (einschließlich Action, Func usw.)
  • Nullable
  • T [] Arrays
  • Benutzerdefinierte Klassen und Strukturen, einschließlich generischer Klassen und Strukturen.

Folgende Klassen- / Strukturmitglieder werden intern geklont:

  • Werte von öffentlichen, nicht schreibgeschützten Feldern
  • Werte von öffentlichen Eigenschaften mit get- und set-Accessoren
  • Sammlungselemente für Typen, die ICollection implementieren

Wie schnell ist es

Die Lösung ist schneller als die Reflexion, da die Mitgliederinformationen nur einmal gesammelt werden müssen, bevor GetClone<T>sie für einen bestimmten Typ zum ersten Mal verwendet werden T.

Es ist auch schneller als eine auf Serialisierung basierende Lösung, wenn Sie mehr als mehrere Instanzen desselben Typs klonen T.

und mehr...

Weitere Informationen zu generierten Ausdrücken finden Sie in der Dokumentation .

Beispiel für eine Debug-Liste für Ausdrucksausdrücke für List<int>:

.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
    System.Collections.Generic.List`1[System.Int32] $source,
    CloneExtensions.CloningFlags $flags,
    System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
    .Block(System.Collections.Generic.List`1[System.Int32] $target) {
        .If ($source == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        .If (
            .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
        ) {
            $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
            ).Invoke((System.Object)$source)
        } .Else {
            $target = .New System.Collections.Generic.List`1[System.Int32]()
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
        ) {
            .Default(System.Void)
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
        ) {
            .Block() {
                $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
                    $source.Capacity,
                    $flags,
                    $initializers)
            }
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
        ) {
            .Block(
                System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
                System.Collections.Generic.ICollection`1[System.Int32] $var2) {
                $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
                $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
                .Loop  {
                    .If (.Call $var1.MoveNext() != False) {
                        .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
                                $var1.Current,
                                $flags,


                         $initializers))
                } .Else {
                    .Break #Label2 { }
                }
            }
            .LabelTarget #Label2:
        }
    } .Else {
        .Default(System.Void)
    };
    .Label
        $target
    .LabelTarget #Label1:
}

}}

Was hat die gleiche Bedeutung wie der folgende C # -Code:

(source, flags, initializers) =>
{
    if(source == null)
        return null;

    if(initializers.ContainsKey(typeof(List<int>))
        target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
    else
        target = new List<int>();

    if((flags & CloningFlags.Properties) == CloningFlags.Properties)
    {
        target.Capacity = target.Capacity.GetClone(flags, initializers);
    }

    if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
    {
        var targetCollection = (ICollection<int>)target;
        foreach(var item in (ICollection<int>)source)
        {
            targetCollection.Add(item.Clone(flags, initializers));
        }
    }

    return target;
}

Ist es nicht so, wie Sie Ihre eigene CloneMethode schreiben würden List<int>?

MarcinJuraszek
quelle
2
Wie stehen die Chancen, dass dies auf NuGet kommt? Es scheint die beste Lösung zu sein. Wie ist es mit NClone zu vergleichen ?
Crush
Ich denke, diese Antwort sollte öfter positiv bewertet werden. Die manuelle Implementierung von ICloneable ist mühsam und fehleranfällig. Die Verwendung von Reflektion oder Serialisierung ist langsam, wenn die Leistung wichtig ist und Sie Tausende von Objekten in kurzer Zeit kopieren müssen.
Nightcoder
Überhaupt nicht, Sie irren sich über Reflexion, Sie sollten dies einfach richtig zwischenspeichern. Überprüfen Sie meine Antwort unten stackoverflow.com/a/34368738/4711853
Roma
31

Nun, ich hatte Probleme mit ICloneable in Silverlight, aber mir gefiel die Idee der Seralisierung. Ich kann XML seralisieren, also habe ich Folgendes getan:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}
Michael White
quelle
31

Wenn Sie bereits eine Drittanbieteranwendung wie ValueInjecter oder Automapper verwenden , können Sie Folgendes tun:

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

Mit dieser Methode müssen Sie weder Ihre Objekte implementieren ISerializablenoch ICloneablebearbeiten. Dies ist beim MVC / MVVM-Muster üblich, daher wurden einfache Tools wie dieses erstellt.

Weitere Informationen finden Sie im Beispiel zum Deep Cloning von ValueInjecter auf GitHub .

Michael Cox
quelle
25

Am besten implementieren Sie eine Erweiterungsmethode wie

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }

und dann überall in der Lösung von verwenden

var copy = anyObject.DeepClone();

Wir können die folgenden drei Implementierungen haben:

  1. Durch Serialisierung (der kürzeste Code)
  2. Durch Reflexion - 5x schneller
  3. Durch Ausdrucksbäume - 20x schneller

Alle verknüpften Methoden funktionieren gut und wurden gründlich getestet.

frakon
quelle
Das Klonen von Code mithilfe von Ausdrucksbäumen, die Sie unter codeproject.com/Articles/1111658/… veröffentlicht haben, schlägt bei neueren Versionen des .Net-Frameworks mit einer Sicherheitsausnahme fehl. Der Vorgang kann die Laufzeit destabilisieren. Dies ist im Grunde eine Ausnahme aufgrund eines fehlerhaften Ausdrucksbaums. Überprüfen Sie, ob Sie eine Lösung haben. Tatsächlich habe ich nur
Probleme
1
Die Implementierung von ExpressionTree scheint sehr gut zu sein. Es funktioniert sogar mit Zirkelverweisen und privaten Mitgliedern. Keine Attribute erforderlich. Beste Antwort, die ich gefunden habe.
N73k
Die beste Antwort hat sehr gut funktioniert, du hast meinen Tag gerettet
Adel Mourad
23

Die kurze Antwort lautet: Sie erben von der ICloneable-Schnittstelle und implementieren dann die .clone-Funktion. Der Klon sollte eine Mitgliedskopie erstellen und eine Tiefenkopie für jedes Mitglied durchführen, das dies erfordert, und dann das resultierende Objekt zurückgeben. Dies ist eine rekursive Operation (es erfordert, dass alle Mitglieder der Klasse, die Sie klonen möchten, entweder Werttypen sind oder ICloneable implementieren und dass ihre Mitglieder entweder Werttypen sind oder ICloneable implementieren usw.).

Weitere Informationen zum Klonen mit ICloneable finden Sie in diesem Artikel .

Die lange Antwort lautet "es kommt darauf an". Wie von anderen erwähnt, wird ICloneable von Generika nicht unterstützt, erfordert spezielle Überlegungen für zirkuläre Klassenreferenzen und wird von einigen tatsächlich als "Fehler" angesehen. in .NET Framework angesehen. Die Serialisierungsmethode hängt davon ab, ob Ihre Objekte serialisierbar sind, was möglicherweise nicht der Fall ist und Sie möglicherweise keine Kontrolle darüber haben. Es gibt immer noch viele Debatten in der Community, über die die "beste" Praxis gesprochen wird. In der Realität ist keine der Lösungen die Einheitsgröße für alle Situationen, für die ICloneable ursprünglich interpretiert wurde.

In diesem Developer's Corner-Artikel finden Sie einige weitere Optionen (Dank an Ian).

Zach Burlingame
quelle
1
ICloneable verfügt nicht über eine generische Schnittstelle, daher wird die Verwendung dieser Schnittstelle nicht empfohlen.
Karg
Ihre Lösung funktioniert so lange, bis Zirkelverweise verarbeitet werden müssen. Dann wird es komplizierter. Versuchen Sie besser, Deep Cloning mithilfe von Deep Serialization zu implementieren.
Pop Catalin
Leider sind auch nicht alle Objekte serialisierbar, sodass Sie diese Methode auch nicht immer verwenden können. Ians Link ist die bislang umfassendste Antwort.
Zach Burlingame
19
  1. Grundsätzlich müssen Sie eine ICloneable-Schnittstelle implementieren und dann das Kopieren der Objektstruktur realisieren.
  2. Wenn es sich um eine tiefe Kopie aller Mitglieder handelt, müssen Sie sicherstellen (unabhängig von der von Ihnen gewählten Lösung), dass auch alle Kinder klonbar sind.
  3. Manchmal müssen Sie sich während dieses Vorgangs einer gewissen Einschränkung bewusst sein. Wenn Sie beispielsweise die ORM-Objekte kopieren, lassen die meisten Frameworks nur ein Objekt zu, das an die Sitzung angehängt ist, und Sie dürfen KEINE Klone dieses Objekts erstellen, oder wenn es möglich ist, müssen Sie sich darum kümmern über das Anhängen von Sitzungen dieser Objekte.

Prost.

Dimarzionist
quelle
4
ICloneable verfügt nicht über eine generische Schnittstelle, daher wird die Verwendung dieser Schnittstelle nicht empfohlen.
Karg
Einfache und prägnante Antworten sind die besten.
DavidGuaita
17

BEARBEITEN: Projekt wird eingestellt

Wenn Sie echtes Klonen auf unbekannte Typen wünschen, können Sie sich fastclone ansehen .

Das ausdrucksbasierte Klonen funktioniert etwa zehnmal schneller als die binäre Serialisierung und behält die vollständige Integrität des Objektgraphen bei.

Das heißt: Wenn Sie in Ihrer Hierarchie mehrmals auf dasselbe Objekt verweisen, wird auf den Klon auch eine einzelne Instanz verwiesen.

Es sind keine Schnittstellen, Attribute oder andere Änderungen an den zu klonenden Objekten erforderlich.

Michael Sander
quelle
Dieser scheint ziemlich nützlich zu sein
LuckyLikey
Es ist einfacher, mit einem Code-Snapshot zu arbeiten als mit dem Gesamtsystem, insbesondere einem geschlossenen. Es ist verständlich, dass keine Bibliothek alle Probleme mit einem Schuss lösen kann. Einige Entspannungen sollten gemacht werden.
TarmoPikaro
1
Ich habe Ihre Lösung ausprobiert und es scheint gut zu funktionieren, danke! Ich denke, diese Antwort sollte öfter positiv bewertet werden. Die manuelle Implementierung von ICloneable ist mühsam und fehleranfällig. Die Verwendung von Reflektion oder Serialisierung ist langsam, wenn die Leistung wichtig ist und Sie Tausende von Objekten in kurzer Zeit kopieren müssen.
Nightcoder
Ich habe es versucht und es hat bei mir überhaupt nicht funktioniert. Löst eine MemberAccess-Ausnahme aus.
Michael Brown
Es funktioniert nicht mit neueren Versionen von .NET und wird eingestellt
Michael Sander
14

Halten Sie die Dinge einfach und verwenden Sie AutoMapper wie bereits erwähnt. Es ist eine einfache kleine Bibliothek, um ein Objekt einem anderen zuzuordnen. Um ein Objekt mit demselben Typ auf ein anderes zu kopieren, benötigen Sie lediglich drei Codezeilen:

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);

Das Zielobjekt ist jetzt eine Kopie des Quellobjekts. Nicht einfach genug? Erstellen Sie eine Erweiterungsmethode, die Sie überall in Ihrer Lösung verwenden können:

public static T Copy<T>(this T source)
{
    T copy = default(T);
    Mapper.CreateMap<T, T>();
    copy = Mapper.Map<T, T>(source);
    return copy;
}

Die Erweiterungsmethode kann wie folgt verwendet werden:

MyType copy = source.Copy();
Gestapelt
quelle
Seien Sie vorsichtig mit diesem, es funktioniert wirklich schlecht. Am Ende wechselte ich zu Johnc Answer, die so kurz wie diese ist und viel besser abschneidet.
Agorilla
1
Dies macht nur eine flache Kopie.
N73k
11

Ich habe mir dies ausgedacht, um ein .NET- Manko zu überwinden , bei dem List <T> manuell tief kopiert werden muss.

Ich benutze das:

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, einen Oneliner zu finden, der dies tut, aber es ist nicht möglich, da Yield nicht in anonymen Methodenblöcken funktioniert.

Besser noch, verwenden Sie den generischen List <T> Cloner:

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

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.

Contango
quelle
Wenn Sie eine Struktur kopieren, erhalten Sie eine flache Kopie. Möglicherweise benötigen Sie noch eine bestimmte Implementierung für eine tiefe Kopie.
Lasse V. Karlsen
@Lasse V. Karlsen. Ja, Sie haben absolut Recht. Ich habe die Antwort aktualisiert, um dies klarer zu machen. Diese Methode kann verwendet werden, um tiefe Kopien von Strukturen und Klassen zu erstellen . Sie können den enthaltenen Beispiel-Demo-Code ausführen, um zu zeigen, wie er ausgeführt wird. Er enthält ein Beispiel für das Deep-Cloning einer verschachtelten Struktur und ein weiteres Beispiel für das Deep-Cloning einer verschachtelten Klasse.
Contango
9

Im Allgemeinen implementieren Sie die ICloneable-Schnittstelle und implementieren Clone selbst. C # -Objekte verfügen über eine integrierte MemberwiseClone-Methode, die eine flache Kopie ausführt, die Ihnen bei allen Grundelementen helfen kann.

Für eine tiefe Kopie kann es auf keinen Fall wissen, wie es automatisch gemacht wird.

HappyDude
quelle
ICloneable verfügt nicht über eine generische Schnittstelle, daher wird die Verwendung dieser Schnittstelle nicht empfohlen.
Karg
8

Ich habe gesehen, dass es auch durch Reflexion umgesetzt wurde. Grundsätzlich gab es eine Methode, mit der die Elemente eines Objekts durchlaufen und entsprechend in das neue Objekt kopiert werden konnten. Als es Referenztypen oder Sammlungen erreichte, hat es sich meiner Meinung nach rekursiv aufgerufen. Reflexion ist teuer, aber es hat ziemlich gut funktioniert.

xr280xr
quelle
8

Hier ist eine Deep Copy-Implementierung:

public static object CloneObject(object opSource)
{
    //grab the type and create a new instance of that type
    Type opSourceType = opSource.GetType();
    object opTarget = CreateInstanceOfType(opSourceType);

    //grab the properties
    PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    //iterate over the properties and if it has a 'set' method assign it from the source TO the target
    foreach (PropertyInfo item in opPropertyInfo)
    {
        if (item.CanWrite)
        {
            //value types can simply be 'set'
            if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
            {
                item.SetValue(opTarget, item.GetValue(opSource, null), null);
            }
            //object/complex types need to recursively call this method until the end of the tree is reached
            else
            {
                object opPropertyValue = item.GetValue(opSource, null);
                if (opPropertyValue == null)
                {
                    item.SetValue(opTarget, null, null);
                }
                else
                {
                    item.SetValue(opTarget, CloneObject(opPropertyValue), null);
                }
            }
        }
    }
    //return the new item
    return opTarget;
}
Dougajmcdonald
quelle
2
Dies sieht aus wie ein Klon in Mitgliedsform, da die Eigenschaften des Referenztyps nicht bekannt sind
sll
1
Wenn Sie eine unglaublich schnelle Leistung wünschen, entscheiden Sie sich nicht für diese Implementierung: Sie verwendet Reflexion, sodass sie nicht so schnell ist. Umgekehrt ist "vorzeitige Optmisierung das Allerbeste". Ignorieren Sie daher die Leistungsseite, bis Sie einen Profiler ausgeführt haben.
Contango
1
CreateInstanceOfType ist nicht definiert?
MonsterMMORPG
Bei der Interger schlägt dies fehl: "Für eine nicht statische Methode ist ein Ziel erforderlich."
Mr.B
8

Da ich in verschiedenen Projekten keinen Kloner finden konnte, der alle meine Anforderungen erfüllt, habe ich einen tiefen Kloner erstellt, der konfiguriert und an verschiedene Codestrukturen angepasst werden kann, anstatt meinen Code an die Anforderungen des Kloners anzupassen. Dies wird erreicht, indem dem zu klonenden Code Anmerkungen hinzugefügt werden, oder Sie lassen den Code einfach so, wie er das Standardverhalten haben soll. Es verwendet Reflection, Typ-Caches und basiert auf schnellerflect . Der Klonprozess ist für eine große Datenmenge und eine hohe Objekthierarchie sehr schnell (im Vergleich zu anderen auf Reflexion / Serialisierung basierenden Algorithmen).

https://github.com/kalisohn/CloneBehave

Auch als Nuget-Paket erhältlich: https://www.nuget.org/packages/Clone.Behave/1.0.0

Beispiel: Der folgende Code führt die DeepClone-Adresse aus, führt jedoch nur eine flache Kopie des Felds _currentJob aus.

public class Person 
{
  [DeepClone(DeepCloneBehavior.Shallow)]
  private Job _currentJob;      

  public string Name { get; set; }

  public Job CurrentJob 
  { 
    get{ return _currentJob; }
    set{ _currentJob = value; }
  }

  public Person Manager { get; set; }
}

public class Address 
{      
  public Person PersonLivingHere { get; set; }
}

Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");

Address adrClone = adr.Clone();

//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
Kalisohn
quelle
7

Code Generator

Wir haben viele Ideen von der Serialisierung über die manuelle Implementierung bis zur Reflexion gesehen, und ich möchte einen völlig anderen Ansatz mit dem CGbR-Codegenerator vorschlagen . Die Generate Clone-Methode ist speicher- und CPU-effizient und daher 300x schneller als der Standard-DataContractSerializer.

Alles was Sie brauchen ist eine teilweise Klassendefinition mit ICloneableund der Generator erledigt den Rest:

public partial class Root : ICloneable
{
    public Root(int number)
    {
        _number = number;
    }
    private int _number;

    public Partial[] Partials { get; set; }

    public IList<ulong> Numbers { get; set; }

    public object Clone()
    {
        return Clone(true);
    }

    private Root()
    {
    }
} 

public partial class Root
{
    public Root Clone(bool deep)
    {
        var copy = new Root();
        // All value types can be simply copied
        copy._number = _number; 
        if (deep)
        {
            // In a deep clone the references are cloned 
            var tempPartials = new Partial[Partials.Length];
            for (var i = 0; i < Partials.Length; i++)
            {
                var value = Partials[i];
                value = value.Clone(true);
                tempPartials[i] = value;
            }
            copy.Partials = tempPartials;
            var tempNumbers = new List<ulong>(Numbers.Count);
            for (var i = 0; i < Numbers.Count; i++)
            {
                var value = Numbers[i];
                tempNumbers.Add(value);
            }
            copy.Numbers = tempNumbers;
        }
        else
        {
            // In a shallow clone only references are copied
            copy.Partials = Partials; 
            copy.Numbers = Numbers; 
        }
        return copy;
    }
}

Hinweis: Die neueste Version enthält mehr Nullprüfungen, aber ich habe sie zum besseren Verständnis weggelassen.

Toxantron
quelle
6

Ich mag solche Copyconstructors:

    public AnyObject(AnyObject anyObject)
    {
        foreach (var property in typeof(AnyObject).GetProperties())
        {
            property.SetValue(this, property.GetValue(anyObject));
        }
        foreach (var field in typeof(AnyObject).GetFields())
        {
            field.SetValue(this, field.GetValue(anyObject));
        }
    }

Wenn Sie weitere Dinge kopieren möchten, fügen Sie diese hinzu

LuckyLikey
quelle
6

Diese Methode löste das Problem für mich:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

Verwenden Sie es so: MyObj a = DeepCopy(b);

JerryGoyal
quelle
6

Hier eine schnelle und einfache Lösung, die für mich funktioniert hat, ohne auf Serialisierung / Deserialisierung zu setzen.

public class MyClass
{
    public virtual MyClass DeepClone()
    {
        var returnObj = (MyClass)MemberwiseClone();
        var type = returnObj.GetType();
        var fieldInfoArray = type.GetRuntimeFields().ToArray();

        foreach (var fieldInfo in fieldInfoArray)
        {
            object sourceFieldValue = fieldInfo.GetValue(this);
            if (!(sourceFieldValue is MyClass))
            {
                continue;
            }

            var sourceObj = (MyClass)sourceFieldValue;
            var clonedObj = sourceObj.DeepClone();
            fieldInfo.SetValue(returnObj, clonedObj);
        }
        return returnObj;
    }
}

EDIT : erfordert

    using System.Linq;
    using System.Reflection;

So habe ich es benutzt

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
Daniele D.
quelle
5

Folge diesen Schritten:

  • Definieren Sie eine ISelf<T>mit einer schreibgeschützten SelfEigenschaft, die zurückgibt T, und ICloneable<out T>die von ISelf<T>einer Methode abgeleitet ist und diese enthält T Clone().
  • Definieren Sie dann einen CloneBaseTyp, der ein protected virtual generic VirtualCloneCasting MemberwiseClonefür den übergebenen Typ implementiert .
  • Jeder abgeleitete Typ sollte implementiert VirtualClonewerden, indem die Basis-Klonmethode aufgerufen wird und dann alles getan wird, um die Aspekte des abgeleiteten Typs, die die übergeordnete VirtualClone-Methode noch nicht verarbeitet hat, ordnungsgemäß zu klonen.

Für eine maximale Vielseitigkeit der Vererbung sollten Klassen, die öffentliche Klonfunktionen sealedbereitstellen, eine Basisklasse sein, die bis auf das Fehlen des Klonens ansonsten identisch ist. Anstatt Variablen vom expliziten klonbaren Typ zu übergeben, nehmen Sie einen Parameter vom Typ ICloneable<theNonCloneableType>. Dies ermöglicht eine Routine, die erwartet, dass eine klonbare Ableitung von Foomit einer klonbaren Ableitung von arbeitet DerivedFoo, ermöglicht aber auch die Erstellung nicht klonbarer Ableitungen von Foo.

Superkatze
quelle
5

Ich denke, Sie können das versuchen.

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
Sudhanva Kotabagi
quelle
4

Ich habe eine Version der akzeptierten Antwort erstellt, die sowohl mit '[Serializable]' als auch mit '[DataContract]' funktioniert. Es ist eine Weile her, seit ich es geschrieben habe, aber wenn ich mich richtig erinnere, brauchte [DataContract] einen anderen Serializer.

Benötigt System, System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml ;

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (typeof(T).IsSerializable == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name="type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 
Jeroen Ritmeijer
quelle
4

Ok, es gibt ein offensichtliches Beispiel für die Reflexion in diesem Beitrag, ABER die Reflexion ist normalerweise langsam, bis Sie anfangen, sie richtig zwischenzuspeichern.

Wenn Sie es richtig zwischenspeichern, wird das 1000000-Objekt um 4,6 Sekunden (gemessen von Watcher) tief geklont.

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

als Sie zwischengespeicherte Eigenschaften nehmen oder neue zum Wörterbuch hinzufügen und sie einfach verwenden

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

Vollständiger Code Check in meinem Beitrag in einer anderen Antwort

https://stackoverflow.com/a/34365709/4711853

Roma Borodov
quelle
2
Das Anrufen prop.GetValue(...)ist immer noch eine Reflexion und kann nicht zwischengespeichert werden. In einem Ausdrucksbaum ist es allerdings so schnell kompiliert
Tseng
4

Da fast alle Antworten auf diese Frage unbefriedigend waren oder in meiner Situation eindeutig nicht funktionieren, habe ich AnyClone verfasst, das vollständig mit Reflexion implementiert und alle Anforderungen hier gelöst hat. Ich konnte die Serialisierung in einem komplizierten Szenario mit komplexer Struktur nicht zum Laufen bringen und IClonableist weniger als ideal - tatsächlich sollte es nicht einmal notwendig sein.

Standard-Ignorierattribute werden unterstützt mit [IgnoreDataMember], [NonSerialized]. Unterstützt komplexe Sammlungen, Eigenschaften ohne Setter, schreibgeschützte Felder usw.

Ich hoffe, es hilft jemand anderem da draußen, der auf die gleichen Probleme gestoßen ist wie ich.

Michael Brown
quelle
4

Haftungsausschluss: Ich bin der Autor des genannten Pakets.

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

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

BinaryFormattererfordert das SerializableAttribut, JsonConvertererfordert einen parameterlosen Konstruktor oder Attribute, behandelt keine schreibgeschützten Felder oder Schnittstellen sehr gut und beide sind 10-30x langsamer als nötig.

Ausdrucksbäume

Sie können stattdessen Ausdrucksbäume oder Reflection.Emit verwenden , um Kloncode nur einmal zu generieren, und dann diesen kompilierten Code anstelle von langsamer Reflektion oder Serialisierung verwenden.

Nachdem ich selbst auf das Problem gestoßen war und keine zufriedenstellende Lösung gefunden hatte, entschied ich mich, ein Paket zu erstellen, das genau das tut und mit jedem Typ funktioniert und fast so schnell ist wie benutzerdefinierter geschriebener Code .

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

Verwendungszweck

Sie können es von NuGet installieren. Holen Sie sich das ObjectClonerPaket und verwenden Sie es als:

var clone = ObjectCloner.DeepClone(original);

oder wenn es Ihnen nichts ausmacht, Ihren Objekttyp mit Erweiterungen zu verschmutzen, erhalten Sie ObjectCloner.Extensionsauch und schreiben Sie:

var clone = original.DeepClone();

Performance

Ein einfacher Benchmark zum Klonen einer Klassenhierarchie zeigte eine Leistung ~ 3x schneller als bei Verwendung von Reflection, ~ 12x schneller als bei der Newtonsoft.Json-Serialisierung und ~ 36x schneller als die empfohlene BinaryFormatter.

Marcell Toth
quelle