Domanda Oggetti di clonazione profonda


Voglio fare qualcosa come:

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

E quindi apportare modifiche al nuovo oggetto che non si riflettono nell'oggetto originale.

Non ho spesso bisogno di questa funzionalità, quindi quando è stato necessario, ho fatto ricorso alla creazione di un nuovo oggetto e quindi a copiare ciascuna proprietà singolarmente, ma mi lascia sempre la sensazione che esista un modo migliore o più elegante di gestire la situazione.

Come posso clonare o copiare in profondità un oggetto in modo che l'oggetto clonato possa essere modificato senza che nessuna modifica venga riflessa nell'oggetto originale?


1827
2017-09-17 00:06


origine


risposte:


Mentre la pratica standard è quella di implementare il ICloneable interfaccia (descritta Qui, quindi non rigurgiterò), ecco una bella copiatrice di oggetti clone profonda che ho trovato Il progetto del codice qualche tempo fa e incorporato nella nostra roba.

Come accennato altrove, richiede che i tuoi oggetti siano serializzabili.

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

L'idea è che serializza il tuo oggetto e poi lo deserializza in un nuovo oggetto. Il vantaggio è che non devi preoccuparti di clonare tutto quando un oggetto diventa troppo complesso.

E con l'uso di metodi di estensione (anche dalla fonte di riferimento originale):

Nel caso in cui preferisci usare il nuovo metodi di estensione di C # 3.0, cambiare il metodo per avere la seguente firma:

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

Ora la chiamata al metodo diventa semplicemente objectBeingCloned.Clone();.

MODIFICARE (10 gennaio 2015) Ho pensato di rivisitare questo, per dire che recentemente ho iniziato a usare (Newtonsoft) Json per fare questo, dovrebbe essere più leggero ed evita il sovraccarico dei tag [Serializable]. (NB @atconway ha sottolineato nei commenti che i membri privati ​​non vengono clonati utilizzando il metodo JSON)

/// <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



Volevo un cloner per oggetti molto semplici, per lo più primitivi e liste. Se il tuo oggetto è fuori dalla scatola serializzabile in JSON, allora questo metodo farà il trucco. Ciò non richiede alcuna modifica o implementazione di interfacce sulla classe clonata, ma solo un serializzatore JSON come 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



La ragione per non usare ICloneable è non perché non ha un'interfaccia generica. La ragione per non usarlo è perché è vaga. Non è chiaro se stai ricevendo una copia superficiale o profonda; questo dipende dall'implementatore.

Sì, MemberwiseClone fa una copia superficiale, ma il contrario di MemberwiseClone non è Clone; sarebbe, forse, DeepClone, che non esiste. Quando si utilizza un oggetto attraverso la sua interfaccia ICloneable, non si può sapere quale tipo di clonazione viene eseguita dall'oggetto sottostante. (E i commenti XML non lo renderanno chiaro, perché otterrete i commenti dell'interfaccia piuttosto che quelli sul metodo Clone dell'oggetto.)

Quello che faccio di solito è semplicemente fare un Copy metodo che fa esattamente quello che voglio.


146
2017-09-26 20:18



Dopo molte letture su molte delle opzioni collegate qui, e possibili soluzioni per questo problema, credo tutte le opzioni sono riassunte abbastanza bene in Ian Pil link (tutte le altre opzioni sono variazioni di quelle) e la migliore soluzione è fornita da Pedro77il link sui commenti delle domande.

Quindi copro solo le parti rilevanti di questi 2 riferimenti qui. In questo modo possiamo avere:

La cosa migliore da fare per la clonazione di oggetti in c sharp!

Innanzitutto, quelle sono tutte le nostre opzioni:

Il articolo Fast Deep Copy di Expression Trees   ha anche un confronto delle prestazioni della clonazione tramite Serialization, Reflection ed Expression Trees.

Perché scelgo ICloneable (cioè manualmente)

Mr Venkat Subramaniam (link ridondante qui) spiega in molti dettagli perché.

Tutto il suo articolo circonda un esempio che cerca di essere applicabile per la maggior parte dei casi, utilizzando 3 oggetti: Persona, Cervello e Città. Vogliamo clonare una persona, che avrà il suo cervello ma la stessa città. È possibile visualizzare tutti i problemi uno qualsiasi degli altri metodi sopra riportati può portare o leggere l'articolo.

Questa è la mia versione leggermente modificata della sua conclusione:

Copia di un oggetto specificando New seguito dal nome della classe porta spesso al codice che non è estensibile. L'uso del clone, l'applicazione del modello prototipo, è un modo migliore per raggiungere questo obiettivo. Tuttavia, l'uso del clone come è fornito in C # (e Java) può essere piuttosto problematico. È meglio fornire un costruttore di copia protetto (non pubblico) e invocarlo dal metodo clone. Questo ci dà la possibilità di delegare il compito di creare un oggetto a un'istanza di una classe stessa, fornendo così estensibilità e anche, creando in sicurezza gli oggetti usando il costruttore di copia protetta.

Speriamo che questa implementazione possa chiarire le cose:

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

Ora considera che una classe deriva da Person.

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

Puoi provare a eseguire il seguente codice:

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

L'output prodotto sarà:

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

Osserva che, se teniamo un conteggio del numero di oggetti, il clone come implementato qui manterrà un conteggio corretto del numero di oggetti.


83
2017-09-17 00:13



Preferisco un costruttore di copia a un clone. L'intento è più chiaro.


69
2018-03-16 11:38



Semplice metodo di estensione per copiare tutte le proprietà pubbliche. Funziona per qualsiasi oggetto e non richiedere che la classe sia [Serializable]. Può essere esteso per altri livelli di accesso.

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



Beh, stavo avendo problemi con l'uso di ICloneable in Silverlight, ma mi piaceva l'idea della seralizzazione, posso seralizzare l'XML, così ho fatto questo:

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



Se stai già utilizzando un'applicazione di terze parti come ValueInjecter o automapper, puoi fare qualcosa del genere:

MyObject oldObj; // The existing object to clone

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

Usando questo metodo non devi implementare ISerializable o ICloneable sui tuoi oggetti. Questo è comune con il pattern MVC / MVVM, quindi sono stati creati strumenti semplici come questo.

vedere la soluzione di clonazione profonda valueinjecter su CodePlex.


26
2017-12-24 22:56