Frage Tief klonen von Objekten


Ich möchte 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 nicht im ursprünglichen Objekt enthalten sind.

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

Wie kann ich ein Objekt klonen oder tiefkopieren, damit das geklonte Objekt geändert werden kann, ohne dass Änderungen am ursprünglichen Objekt vorgenommen werden?


1827
2017-09-17 00:06


Ursprung


Antworten:


Während die gängige Praxis darin besteht, die ICloneable Schnittstelle (beschrieben Hier, also werde ich nicht erbrechen), hier ist ein netter Klon-Objektkopierer, den ich gefunden habe Das Code-Projekt vor einer Weile und in unsere Sachen eingebaut.

Wie bereits an anderer Stelle erwähnt, müssen Ihre Objekte serialisiert werden.

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.", "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 zu einem neuen 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):

Falls Sie das neue bevorzugen 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 objectBeingCloned.Clone();.

BEARBEITEN (10. Januar 2015) Dachte, ich würde dies noch einmal Revue passieren lassen, um zu erwähnen, dass ich vor kurzem (Newtonsoft) Json damit angefangen habe, dies zu tun 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 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);
}

1466
2018-04-03 13:31



Ich wollte einen Cloner für sehr einfache Objekte von meist Primitiven und Listen. Wenn Ihr Objekt JSON serialisierbar ist, wird diese Methode den Zweck erfüllen. Dies erfordert keine Modifikation oder Implementierung von Schnittstellen in der geklonten Klasse, nur ein JSON-Serializer wie JSON.NET.

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

181
2017-09-17 01:12



Der Grund, nicht zu verwenden ICloneable ist nicht weil es keine generische Schnittstelle hat. Der Grund, es nicht zu benutzen, ist, weil es vage ist. Es wird nicht klar, ob Sie eine seichte oder eine tiefe Kopie erhalten. Das liegt an dem Implementierer.

Ja, MemberwiseClone macht eine flache Kopie, aber das Gegenteil von MemberwiseClone ist nicht Clone; es wäre vielleicht, DeepClone, die nicht existiert. Wenn Sie ein Objekt über seine ICloneable-Schnittstelle verwenden, können Sie nicht wissen, welche Art von Klonen das zugrunde liegende Objekt ausführt. (Und XML-Kommentare machen das nicht klar, weil Sie die Kommentare der Benutzeroberfläche erhalten und nicht die der Clone-Methode des Objekts.)

Was ich normalerweise mache, ist einfach ein Copy Methode, die genau das tut, was ich will.


146
2017-09-26 20:18



Nach vielen Lektüre über viele der hier verbundenen Optionen und mögliche Lösungen für dieses Problem glaube ich Alle Optionen sind ziemlich gut zusammengefasst bei Ian P.'s Link (alle anderen Optionen sind Variationen davon) und die beste Lösung wird von Pedro77's Link zu den Fragekommentaren.

Ich werde nur relevante Teile dieser 2 Referenzen hier kopieren. So können wir haben:

Das beste zum Klonen von Objekten in cis!

Das sind vor allem unsere Möglichkeiten:

Das Artikel Schnelle Tiefenkopie von Expression Trees   hat auch einen Leistungsvergleich des Klonens durch Serialisierung, Reflexion und Expression Trees.

Warum ich wähle ICloneable (d. h. manuell)

Herr Venkat Subramaniam (redundanter Link hier) erklärt sehr detailliert warum.

All sein Artikel kreist um ein Beispiel, das für die meisten Fälle mit 3 Objekten anwendbar ist: 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 einer der anderen oben genannten Methoden zum Lesen oder Lesen des Artikels bringen kann.

Dies ist meine leicht modifizierte Version seiner Schlussfolgerung:

Kopieren eines Objekts durch Angabe von New gefolgt von dem Klassennamen führt oft zu Code, der nicht erweiterbar ist. Mit Clone ist die Anwendung des Prototypmusters ein besserer Weg, dies zu erreichen. Die Verwendung von Clone, wie es in C # (und Java) bereitgestellt wird, kann jedoch auch ziemlich problematisch sein. Es ist besser, einen geschützten (nicht öffentlichen) Kopierkonstruktor bereitzustellen und diesen aus der Klonmethode aufzurufen. Dies gibt uns die Möglichkeit, die Aufgabe des Erzeugens eines Objekts an eine Instanz einer Klasse selbst zu delegieren, wodurch eine Erweiterbarkeit bereitgestellt wird und die Objekte unter Verwendung des Konstruktors für geschützte Kopien sicher erzeugt werden.

Hoffentlich kann diese Implementierung klarstellen:

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

Ziehen Sie in Betracht, eine Klasse von Person abzuleiten.

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 produzierte Produktion 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 Clone, der hier implementiert wird, die Anzahl der Objekte korrekt zählt, wenn wir die Anzahl der Objekte zählen.


83
2017-09-17 00:13



Ich bevorzuge einen Kopierkonstruktor zu einem Klon. Die Absicht ist klarer.


69
2018-03-16 11:38



Einfache Erweiterungsmethode zum Kopieren aller öffentlichen Eigenschaften. Funktioniert für beliebige Objekte und nicht Klasse erforderlich sein [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 ) } );
        }
    };
}

36
2017-12-02 17:39



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

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

28
2017-10-15 17:55



Wenn Sie bereits eine Drittanbieteranwendung wie ValueInjektion oder Automapper, Sie können so etwas tun:

MyObject oldObj; // The existing object to clone

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

Mit dieser Methode müssen Sie ISerializable oder ICloneable für Ihre Objekte nicht implementieren. Dies ist üblich mit dem MVC / MVVM-Muster, also wurden einfache Werkzeuge wie dieses erstellt.

sehen Die ValueInjecter Deep Cloning Lösung auf CodePlex.


26
2017-12-24 22:56