2638 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?

110 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...

27 Stimmen

Werfen Sie einen Blick auf AutoMapper

1922voto

johnc Punkte 38025

Ein Ansatz ist die Umsetzung der ICloneable Schnittstelle (beschrieben aquí (damit ich nicht alles wiederkäue), hier ist ein schöner Deep Clone Object Copier, den ich auf Das Code-Projekt vor einiger Zeit und haben sie in unseren Code integriert. 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 via serialization.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>A deep copy of the 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 (ReferenceEquals(source, null)) return default;

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

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

Falls Sie es vorziehen, die neue Erweiterungsmethoden von C# 3.0, ändern Sie die Methode, um die folgende Signatur zu haben:

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

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

EDIT (10. Januar 2015) Ich dachte, ich würde dieses Thema noch einmal aufgreifen, um zu erwähnen, dass ich vor kurzem begonnen habe, (Newtonsoft) Json zu verwenden, um dies zu tun, es sollte sein leichter und vermeidet den Overhead von [Serializable]-Tags. ( 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 serialization 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 (ReferenceEquals(source, null)) return default;

    // 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);
}

30 Stimmen

stackoverflow.com/questions/78536/cloning-objects-in-c/ hat einen Link zu dem obigen Code [und verweist auf zwei weitere derartige Implementierungen, von denen eine in meinem Kontext besser geeignet ist]

121 Stimmen

Die Serialisierung/Deserialisierung ist mit erheblichem Overhead verbunden, der nicht notwendig ist. Siehe die ICloneable-Schnittstelle und .MemberWise()-Klonmethoden in C#.

22 Stimmen

@David, zugegeben, aber wenn es sich um leichte Objekte handelt und die Leistungseinbußen für Ihre Anforderungen nicht zu hoch sind, dann ist es ein nützlicher Tipp. Ich habe es noch nicht intensiv mit großen Datenmengen in einer Schleife verwendet, aber ich habe noch nie ein Leistungsproblem gesehen.

433voto

craastad Punkte 5584

Ich wollte einen Kloner für sehr einfache Objekte, meist Primitive und Listen. Wenn Ihr Objekt von Haus aus JSON-serialisierbar ist, dann wird diese Methode den Zweck erfüllen. 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 auch diese Erweiterungsmethode verwenden

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

21 Stimmen

Ist die Lösung sogar schneller als die BinaryFormatter-Lösung, .NET Serialisierung Leistungsvergleich

5 Stimmen

Vielen Dank dafür. Ich war in der Lage, im Wesentlichen die gleiche Sache mit dem BSON-Serializer zu tun, die mit dem MongoDB-Treiber für C# geliefert wird.

6 Stimmen

Das ist für mich der beste Weg, aber ich verwende Newtonsoft.Json.JsonConvert aber es ist dasselbe

203voto

Ryan Lundy Punkte 195074

Der Grund für die Nichtverwendung ICloneable es no weil es keine generische Schnittstelle hat. Der Grund, ihn nicht zu verwenden, ist, dass er vage ist. . Es ist nicht klar, ob es sich um eine flache oder eine tiefe Kopie handelt; das bleibt dem Implementierer überlassen.

Ja, MemberwiseClone macht eine oberflächliche Kopie, aber das Gegenteil von MemberwiseClone n'est pas Clone Vielleicht wäre es das, DeepClone die 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 durchführt. (Und die XML-Kommentare machen es nicht klar, weil Sie die Schnittstellenkommentare erhalten und nicht die der Clone-Methode des Objekts).

Normalerweise mache ich einfach eine Copy Methode, die genau das tut, was ich will.

0 Stimmen

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 jede Ebene des tiefen und flachen Kopierens durchführen sollte, die notwendig ist, damit das neue Dictionary ein unabhängiges Dictionary ist, das die gleichen T's und U's (Strukturinhalte und/oder Objektreferenzen) wie das Original enthält. Wo ist die Zweideutigkeit? Sicherlich wäre ein generisches ICloneable(Of T), das ISelf(Of T) vererbt, das eine "Self"-Methode enthält, viel besser, aber ich sehe keine Zweideutigkeit in Bezug auf tiefes oder seichtes Klonen.

45 Stimmen

Ihr Beispiel veranschaulicht das Problem. Nehmen wir an, Sie haben ein Dictionary<string, Customer>. Sollte das geklonte Dictionary die même Kundenobjekte wie das Original, oder Kopien dieser Kundenobjekte? Es gibt vernünftige Anwendungsfälle für beide. Aber ICloneable macht nicht klar, welchen Sie bekommen werden. Deshalb ist es nicht nützlich.

0 Stimmen

@Kyralessa Im MSDN-Artikel von Microsoft wird genau dieses Problem genannt, dass man nicht weiß, ob man eine tiefe oder eine flache Kopie anfordert.

161voto

cregox Punkte 16639

Nach eingehender Lektüre vieler der hier verlinkten Optionen und möglicher Lösungen für dieses Problem bin ich der Meinung alle Optionen sind gut zusammengefasst unter Ian P. Der Link (alle anderen Optionen sind Abwandlungen dieser Optionen) und die beste Lösung wird durch Pedro77 Der Link auf die Kommentare zur Frage.

Daher kopiere ich hier nur die relevanten Teile dieser 2 Referenzen. Auf diese Weise können wir haben:

Das Beste für das Klonen von Objekten in C sharp!

Das sind in erster Linie alle unsere Möglichkeiten:

El Artikel Schnelle tiefe Kopie durch Ausdrucksbäume hat auch einen Leistungsvergleich des Klonens durch Serialisierung, Reflection und Expression Trees.

Warum ich mich für ICloneable (d.h. manuell)

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

Sein ganzer Artikel dreht sich um ein Beispiel, das versucht, für die meisten Fälle anwendbar zu sein, unter Verwendung von 3 Objekten: Person , Gehirn y Stadt . Wir wollen eine Person klonen, die ihr eigenes Gehirn, aber dieselbe Stadt hat. Sie können sich entweder alle Probleme vorstellen, die eine der oben genannten Methoden mit sich bringen kann, oder den Artikel lesen.

Dies ist meine leicht veränderte Version seiner Schlussfolgerung:

Kopieren eines Objekts durch Angabe von New gefolgt vom Klassennamen führt oft zu Code, der nicht erweiterbar ist. Die Verwendung von clone, der Anwendung des Prototypenmusters, ist ein besserer Weg, dies zu erreichen. Allerdings kann die Verwendung von clone, wie sie in C# (und Java) vorgesehen ist, auch ziemlich problematisch sein. Es ist besser, einen geschützten (nicht-öffentlichen) Kopierkonstruktor bereitzustellen und diesen von der clone-Methode aus aufzurufen. Dies gibt uns die Möglichkeit, die Aufgabe der Erstellung eines Objekts an eine Instanz einer Klasse selbst zu delegieren, wodurch die Erweiterbarkeit und auch die sichere Erstellung der Objekte mit dem geschützten Kopierkonstruktor gewährleistet wird.

Ich hoffe, dass diese Umsetzung Klarheit schaffen kann:

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 wir 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);
    }
}

Das Ergebnis wird sein:

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 eine korrekte Zählung der Objekte vornimmt, wenn wir die Anzahl der Objekte zählen.

9 Stimmen

MS empfiehlt, nicht zu verwenden ICloneable für öffentliche Mitglieder. "Da sich der Aufrufer von Clone nicht darauf verlassen kann, dass die Methode einen vorhersehbaren Klonvorgang durchführt, empfehlen wir, ICloneable nicht in öffentlichen APIs zu implementieren." msdn.microsoft.com/de-us/library/ Aufgrund der von Venkat Subramaniam in dem von Ihnen verlinkten Artikel gegebenen Erklärung halte ich es jedoch für sinnvoll, in dieser Situation solange die Ersteller der ICloneable-Objekte genau wissen, welche Eigenschaften als tiefe und welche als flache Kopien gelten sollen (d.h. Deep Copy Brain, shallow Copy City)

0 Stimmen

Zunächst einmal bin ich weit davon entfernt, ein Experte auf diesem Gebiet (öffentliche APIs) zu sein. I piense en Diese Bemerkung von MS ist ausnahmsweise einmal sehr sinnvoll. Und ich glaube nicht, dass es sicher ist, anzunehmen, dass die Benutzer dieser API ein so tiefes Verständnis haben wird. Daher ist es nur sinnvoll, die Implementierung auf einer öffentliche API wenn es für denjenigen, der es benutzen wird, wirklich keine Rolle spielt. I erraten Eine Art UML, die die Unterscheidung zwischen den einzelnen Eigenschaften sehr deutlich macht, könnte helfen. Aber ich würde gerne von jemandem mit mehr Erfahrung hören :P

0 Stimmen

Sie können die CGbR Klon-Generator und erhalten ein ähnliches Ergebnis, ohne den Code manuell schreiben zu müssen.

97voto

Nick Punkte 13044

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

7 Stimmen

.Net hat keine Kopierkonstruktoren.

57 Stimmen

Natürlich tut es das: new MyObject(objToCloneFrom) Deklarieren Sie einfach einen ctor, der das zu klonende Objekt als Parameter annimmt.

38 Stimmen

Das ist nicht dasselbe. Sie müssen es jeder Klasse manuell hinzufügen, und Sie wissen nicht einmal, ob Sie eine tiefe Kopie garantieren können.

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