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

49voto

Einfache Erweiterungsmethode zum Kopieren aller öffentlichen Eigenschaften. Funktioniert für alle Objekte und nicht erfordern Klasse zu sein [Serializable] . Kann für andere Zugangsstufen 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 ) } );
        }
    };
}

21 Stimmen

Das ist leider nicht ganz richtig. Es ist gleichbedeutend mit dem Aufruf von objectOne.MyProperty = objectTwo.MyProperty (d. h., es wird nur der Verweis kopiert). Es werden nicht die Werte der Eigenschaften geklont.

1 Stimmen

An Alex Norcliffe: Der Autor der Frage fragte nach dem "Kopieren jeder Eigenschaft" und nicht nach dem Klonen. In den meisten Fällen ist eine exakte Duplizierung von Eigenschaften nicht erforderlich.

2 Stimmen

Ich denke über die Verwendung dieser Methode, aber mit Rekursion. also wenn der Wert einer Eigenschaft eine Referenz ist, erstellen Sie ein neues Objekt und rufen Sie CopyTo erneut. ich sehe nur ein Problem, dass alle verwendeten Klassen einen Konstruktor ohne Parameter haben müssen. Hat das schon jemand ausprobiert? ich frage mich auch, ob das tatsächlich mit Eigenschaften funktioniert, die .net-Klassen wie DataRow und DataTable enthalten?

39voto

MarcinJuraszek Punkte 120867

Ich habe gerade eine CloneExtensions Bibliothek Projekt. Es führt ein schnelles, tiefes Klonen durch, indem es einfache Zuweisungsoperationen verwendet, die von der Expression Tree-Laufzeitcodekompilierung erzeugt werden.

Wie wird es verwendet?

Anstatt Ihr eigenes Buch zu schreiben Clone o Copy Methoden mit einem Ton von Zuweisungen zwischen Feldern und Eigenschaften machen das Programm es für sich selbst, mit Expression Tree. GetClone<T>() Methode, die als Erweiterungsmethode gekennzeichnet ist, können Sie sie einfach in Ihrer Instanz aufrufen:

var newInstance = source.GetClone();

Sie können wählen, was kopiert werden soll von source a newInstance mit CloningFlags enum:

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

Was kann geklont werden?

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

Die folgenden Klassen-/Strukturmitglieder werden intern geklont:

  • Werte von öffentlichen, nicht schreibgeschützten Feldern
  • Werte öffentlicher Eigenschaften mit Get- und Set-Accessors
  • Sammlungselemente für Typen, die ICollection implementieren

Wie schnell ist es?

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

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

und mehr...

Lesen Sie mehr über generierte Ausdrücke auf Dokumentation .

Beispiel für die Debug-Auflistung eines Ausdrucks 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 die gleiche Bedeutung hat 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, als würden Sie Ihr eigenes Buch schreiben? Clone Methode für List<int> ?

2 Stimmen

Wie groß sind die Chancen, dass dies in NuGet aufgenommen wird? Es scheint die beste Lösung zu sein. Wie sieht es aus im Vergleich zu NClone ?

0 Stimmen

Ich denke, diese Antwort sollte öfters hochgevotet werden. Die manuelle Implementierung von ICloneable ist mühsam und fehleranfällig, die Verwendung von Reflection oder Serialisierung ist langsam, wenn die Leistung wichtig ist und Sie Tausende von Objekten in einem kurzen Zeitraum kopieren müssen.

0 Stimmen

Überhaupt nicht, Sie falsch über die Reflexion, sollten Sie einfach Cache dies richtig. Prüfen Sie meine Antwort unten stackoverflow.com/a/34368738/4711853

38voto

Michael Cox Punkte 1273

Wenn Sie bereits eine Anwendung eines Drittanbieters wie WertInjektionsgerät o Automapper können Sie etwa so vorgehen:

MyObject oldObj; // The existing object to clone

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

Bei dieser Methode müssen Sie keine ISerializable o ICloneable auf Ihre Objekte. Dies ist beim MVC/MVVM-Muster üblich, weshalb einfache Tools wie dieses entwickelt wurden.

siehe das ValueInjecter Deep Cloning-Beispiel auf GitHub .

37voto

frakon Punkte 1538

Am besten ist die Einführung eines Erweiterungsmethode wie

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

und verwenden Sie es dann an beliebiger Stelle in der Lösung durch

var copy = anyObject.DeepClone();

Es gibt die folgenden drei Umsetzungen:

  1. Durch Serialisierung (der kürzeste Code)
  2. Durch Reflexion - 5x schneller
  3. Mit Expression Trees - 20x schneller

Alle damit verbundenen Methoden funktionieren gut und wurden eingehend getestet.

2 Stimmen

Klonen von Code unter Verwendung von Expression Trees, die Sie veröffentlicht haben codeproject.com/Artikel/1111658/ schlägt bei neueren Versionen von .Net Framework mit einer Sicherheitsausnahme fehl, Operation könnte die Laufzeit destabilisieren Es ist im Grunde eine Ausnahme aufgrund eines fehlerhaften Ausdrucksbaums, der verwendet wird, um die Func zur Laufzeit zu generieren, bitte prüfen Sie, ob Sie eine Lösung haben.ich habe das Problem nur bei komplexen Objekten mit tiefer Hierarchie gesehen, einfache werden einfach kopiert

2 Stimmen

ExpressionTree Implementierung scheint sehr gut. Es funktioniert sogar mit zirkulären Referenzen und privaten Mitgliedern. Keine Attribute erforderlich. Die beste Antwort, die ich gefunden habe.

0 Stimmen

Die beste Antwort, hat sehr gut funktioniert, Sie haben mir den Tag gerettet

37voto

Michael White Punkte 377

Nun, ich hatte Probleme mit ICloneable in Silverlight, aber ich mochte die Idee der Seralisierung, ich kann XML seralisieren, so tat ich dies:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    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);
    }
}

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