Oggetti di clonazione profonda


2226

Voglio fare qualcosa del tipo:

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.

Spesso non ho bisogno di questa funzionalità, quindi quando è stato necessario, ho fatto ricorso alla creazione di un nuovo oggetto e quindi alla copia di ciascuna proprietà singolarmente, ma mi viene sempre la sensazione che esista un modo migliore o più elegante di gestione la situazione.

Come posso clonare o copiare in profondità un oggetto in modo che l'oggetto clonato possa essere modificato senza che le modifiche si riflettano sull'oggetto originale?


81
Può essere utile: "Perché copiare un oggetto è una cosa terribile da fare?" agiledeveloper.com/articles/cloning072002.htm
Pedro77


18
Dovresti dare un'occhiata a AutoMapper
Daniel Little,

3
La tua soluzione è molto più complessa, mi sono perso leggendolo ... hehehe. Sto usando un'interfaccia DeepClone. interfaccia pubblica IDeepCloneable <T> {T DeepClone (); }
Pedro77,

1
@ Pedro77 - Anche se, in modo interessante, quell'articolo finisce per dire di creare un clonemetodo sulla classe, quindi chiamalo un costruttore interno, privato che viene passato this. Quindi copiare è terribile [sic], ma copiarlo attentamente (e sicuramente vale la pena leggere l'articolo). ; ^)
ruffin,

Risposte:


1715

Mentre la pratica standard è quella di implementare l' ICloneableinterfaccia (descritta qui , quindi non rigurgito), ecco una bella copiatrice di oggetti clone profondo che ho trovato su The Code Project qualche tempo fa e l'ha incorporata nelle nostre cose.

Come menzionato 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.", 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);
        }
    }
}

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 originariamente referenziata):

Nel caso in cui si preferisca utilizzare i nuovi metodi di estensione di C # 3.0, modificare il metodo per avere la seguente firma:

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

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

EDIT (10 gennaio 2015) Pensavo di rivederlo, per menzionare che di recente ho iniziato a utilizzare (Newtonsoft) Json per farlo, dovrebbe essere più leggero ed evitare l'overhead dei tag [Serializable]. ( NB @atconway ha sottolineato nei commenti che i membri privati ​​non vengono clonati usando 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);
}

24
stackoverflow.com/questions/78536/cloning-objects-in-c/… ha un link al codice sopra [e fa riferimento ad altre due implementazioni di questo tipo, una delle quali è più appropriata nel mio contesto]
Ruben Bartelink

102
La serializzazione / deserializzazione comporta un notevole sovraccarico non necessario. Vedi l'interfaccia ICloneable e i metodi di clonazione .MemberWise () in C #.
3Daveva il

18
@David, garantito, ma se gli oggetti sono leggeri e le prestazioni colpite durante l'utilizzo non sono troppo elevate per le tue esigenze, allora è un suggerimento utile. Non l'ho usato intensamente con grandi quantità di dati in un ciclo, lo ammetto, ma non ho mai visto un singolo problema di prestazioni.
johnc,

16
@Amir: in realtà no: typeof(T).IsSerializableè vero anche se il tipo è stato contrassegnato con l' [Serializable]attributo. Non deve implementare l' ISerializableinterfaccia.
Daniel Gehriger,

11
Ho pensato di menzionare che, sebbene questo metodo sia utile, e l'ho usato da solo molte volte, non è affatto compatibile con Medium Trust, quindi fai attenzione se stai scrivendo codice che necessita di compatibilità. BinaryFormatter accede ai campi privati ​​e quindi non può funzionare nel set di autorizzazioni predefinito per ambienti di attendibilità parziale. Potresti provare un altro serializzatore, ma assicurati che il tuo chiamante sappia che il clone potrebbe non essere perfetto se l'oggetto in arrivo si basa su campi privati.
Alex Norcliffe,

298

Volevo un clonatore per oggetti molto semplici per lo più primitivi ed elenchi. Se il tuo oggetto è fuori dagli schemi JSON serializzabile, 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);
}

Inoltre, è possibile utilizzare questo metodo di estensione

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

13
il solutiojn è persino più veloce della soluzione BinaryFormatter, confronto delle prestazioni della serializzazione .NET
esskar

3
Grazie per questo. Sono stato in grado di fare essenzialmente la stessa cosa con il serializzatore BSON fornito con il driver MongoDB per C #.
Mark Ewer,

3
Questo è il modo migliore per me, tuttavia, lo uso Newtonsoft.Json.JsonConvertma è lo stesso
Pierre

1
Affinché ciò funzioni, l'oggetto da clonare deve essere serializzabile come già accennato - questo significa anche che potrebbe non avere dipendenze circolari
radomeit

2
Penso che questa sia la soluzione migliore poiché l'implementazione può essere applicata sulla maggior parte dei linguaggi di programmazione.
5

178

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

Sì, MemberwiseClonefa una copia superficiale, ma il contrario di MemberwiseClonenon lo è Clone; sarebbe, forse DeepClone, che non esiste. Quando usi un oggetto attraverso la sua interfaccia ICloneable, non puoi sapere quale tipo di clonazione esegue l'oggetto sottostante. (E i commenti XML non lo chiariranno, perché otterrai i commenti dell'interfaccia piuttosto che quelli sul metodo Clone dell'oggetto.)

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


Non sono chiaro perché ICloneable sia considerato vago. Dato un tipo come Dictionary (Of T, U), mi aspetto che ICloneable.Clone dovrebbe fare qualunque livello di copia profonda e superficiale sia necessario per rendere il nuovo dizionario un dizionario indipendente che contiene gli stessi T e U (contenuti della struttura, e / o riferimenti a oggetti) come l'originale. Dov'è l'ambiguità? A dire il vero, un generico ICloneable (Of T), che ha ereditato ISelf (Of T), che includeva un metodo "Self", sarebbe molto meglio, ma non vedo ambiguità sulla clonazione profonda vs superficiale.
supercat

31
Il tuo esempio illustra il problema. Supponiamo di avere un dizionario <stringa, cliente>. Il dizionario clonato dovrebbe avere gli stessi oggetti cliente dell'originale o copie di tali oggetti cliente? Ci sono casi d'uso ragionevoli per uno dei due. Ma ICloneable non chiarisce quale otterrai. Ecco perché non è utile.
Ryan Lundy,

@Kyralessa L'articolo di Microsoft MSDN afferma in realtà questo problema di non sapere se si sta richiedendo una copia profonda o superficiale.
cotta

La risposta dal duplicato stackoverflow.com/questions/129389/… descrive l'estensione della copia, basata
sull'abbonamento

123

Dopo molte letture su molte delle opzioni collegate qui e possibili soluzioni per questo problema, credo che tutte le opzioni siano riassunte abbastanza bene al link di Ian P (tutte le altre opzioni sono variazioni di quelle) e la soluzione migliore è fornita da Link di Pedro77 sui commenti alla domanda.

Quindi copierò solo le parti pertinenti di quei 2 riferimenti qui. In questo modo possiamo avere:

La cosa migliore da fare per clonare oggetti in C sharp!

Innanzitutto, queste sono tutte le nostre opzioni:

L' 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)

Venkat Subramaniam (link ridondante qui) spiega in dettaglio perché .

Tutto il suo articolo ruota attorno a un esempio che cerca di essere applicabile nella maggior parte dei casi, usando 3 oggetti: Persona , Cervello e Città . Vogliamo clonare una persona, che avrà il suo cervello ma la stessa città. Puoi immaginare tutti i problemi che uno degli altri metodi precedenti può portare o leggere l'articolo.

Questa è la mia versione leggermente modificata della sua conclusione:

Copiare un oggetto specificando Newseguito dal nome della classe porta spesso a codice che non è estensibile. L'uso del clone, l'applicazione del modello prototipo, è un modo migliore per raggiungere questo obiettivo. Tuttavia, l'utilizzo di clone come fornito in C # (e Java) può anche essere abbastanza problematico. È meglio fornire un costruttore di copie protetto (non pubblico) e richiamarlo 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 copie protette.

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 di avere una classe derivata da Persona.

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

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


6
MS consiglia di non utilizzare ICloneableper i membri pubblici. "Poiché i chiamanti di Clone non possono dipendere dal metodo che esegue un'operazione di clonazione prevedibile, si consiglia di non implementare ICloneable nelle API pubbliche." msdn.microsoft.com/en-us/library/… Tuttavia, in base alla spiegazione fornita da Venkat Subramaniam nel tuo articolo collegato, penso che abbia senso utilizzarlo in questa situazione purché i creatori degli oggetti ICloneable abbiano una profondità comprensione di quali proprietà dovrebbero essere profonde o copie superficiali (ovvero copia profonda del cervello, copia superficiale della città)
BateTech

Prima di tutto, sono tutt'altro che un esperto in questo argomento (API pubbliche). Io penso per una volta che la SM osservazione fa un sacco di senso. E non penso che sia sicuro presumere che gli utenti di quell'API avranno una comprensione così profonda. Quindi, ha senso implementarlo su un'API pubblica solo se non importa davvero per chi lo utilizzerà. Mi immagino avere un qualche tipo di UML in modo molto esplicito rendendo la distinzione su ogni proprietà potrebbe aiutare. Ma mi piacerebbe sentire qualcuno con più esperienza. : P
cregox,

È possibile utilizzare CGbR Clone Generator e ottenere un risultato simile senza scrivere manualmente il codice.
Toxantron,

L'implementazione del linguaggio intermedio è utile
Michael Freidgeim il

Non c'è finale in C #
Konrad il

84

Preferisco un costruttore di copie a un clone. L'intenzione è più chiara.


5
.Net non ha costruttori di copie.
Pop Catalin,

48
Sicuro: new MyObject (objToCloneFrom) Dichiara semplicemente un ctor che accetta l'oggetto come clone come parametro.
Nick,

30
Non è la stessa cosa Devi aggiungerlo manualmente a ogni classe e non sai nemmeno se stai garantendo una copia approfondita.
Dave Van den Eynde,

15
+1 per copia ctor. Devi scrivere manualmente una funzione clone () anche per ogni tipo di oggetto, e buona fortuna quando la tua gerarchia di classi raggiunge qualche livello di profondità.
Andrew Grant,

3
Con i costruttori di copie, però, perdi la gerarchia. agiledeveloper.com/articles/cloning072002.htm
Will

42

Semplice metodo di estensione per copiare tutte le proprietà pubbliche. Funziona con qualsiasi oggetto e non richiede 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 ) } );
        }
    };
}

15
Questo, sfortunatamente, è imperfetto. È equivalente a chiamare objectOne.MyProperty = objectTwo.MyProperty (cioè copierà semplicemente il riferimento su). Non clonerà i valori delle proprietà.
Alex Norcliffe,

1
ad Alex Norcliffe: l'autore della domanda ha chiesto di "copiare ogni proprietà" piuttosto che di clonarla. nella maggior parte dei casi non è necessaria la duplicazione esatta delle proprietà.
Konstantin Salavatov,

1
penso di usare questo metodo ma con la ricorsione. quindi se il valore di una proprietà è un riferimento, crea un nuovo oggetto e chiama nuovamente CopyTo. vedo solo un problema, che tutte le classi utilizzate devono avere un costruttore senza parametri. Qualcuno l'ha già provato? mi chiedo anche se questo funzionerà effettivamente con proprietà contenenti classi .net come DataRow e DataTable?
Koryu,

33

Ho appena creato un progetto di CloneExtensionsbiblioteca . Esegue cloni veloci e profondi utilizzando semplici operazioni di assegnazione generate dalla compilazione del codice di runtime di Expression Tree.

Come usarlo?

Invece di scrivere i tuoi metodi Cloneo Copycon un tono di assegnazioni tra campi e proprietà, fai sì che il programma lo faccia per te, usando Expression Tree. GetClone<T>()Il metodo contrassegnato come metodo di estensione ti consente semplicemente di chiamarlo sulla tua istanza:

var newInstance = source.GetClone();

Puoi scegliere da cosa copiare sourceper newInstanceusare CloningFlagsenum:

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

Cosa può essere clonato?

  • Primitivi (int, uint, byte, double, char, ecc.), Tipi immutabili noti (DateTime, TimeSpan, String) e delegati (inclusi Action, Func, ecc.)
  • nullable
  • Matrici []
  • Classi e strutture personalizzate, comprese le classi e le strutture generiche.

I seguenti membri di classe / struct vengono clonati internamente:

  • Valori di campi pubblici, non di sola lettura
  • Valori di proprietà pubbliche con entrambi gli accessi get e set
  • Articoli da collezione per i tipi che implementano ICollection

Quanto è veloce?

La soluzione è più rapida della riflessione, poiché le informazioni dei membri devono essere raccolte una sola volta, prima di GetClone<T>essere utilizzate per la prima volta per un determinato tipo T.

È anche più veloce della soluzione basata sulla serializzazione quando si clonano più di due istanze dello stesso tipo T.

e altro ancora ...

Ulteriori informazioni sulle espressioni generate sulla documentazione .

Elenco di debug di espressioni di esempio per 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:
}

}

cosa ha lo stesso significato del seguente codice c #:

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

Non è abbastanza come scrivere il tuo Clonemetodo per List<int>?


2
Quali sono le possibilità che questo accada su NuGet? Sembra la soluzione migliore. Come si confronta con NClone ?
cotta

Penso che questa risposta debba essere votata più volte. L'implementazione manuale di ICloneable è noiosa e soggetta a errori, l'utilizzo della riflessione o della serializzazione è lento se le prestazioni sono importanti e è necessario copiare migliaia di oggetti in un breve periodo di tempo.
nightcoder

Niente affatto, ti sbagli sulla riflessione, dovresti semplicemente memorizzarlo nella cache correttamente. Controllare la mia risposta qui sotto stackoverflow.com/a/34368738/4711853
Roma Borodov

31

Beh, ho avuto problemi con l'utilizzo di ICloneable in Silverlight, ma mi è piaciuta l'idea della seralizzazione, posso serializzare XML, quindi 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);
    }
}

31

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

Utilizzando questo metodo non è necessario implementare ISerializableo ICloneablesui tuoi oggetti. Questo è comune con il modello MVC / MVVM, quindi sono stati creati strumenti semplici come questo.

vedere l'esempio della clonazione profonda di ValueInjecter su GitHub .


26

Il migliore è implementare un metodo di estensione come

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

e poi usalo ovunque nella soluzione

var copy = anyObject.DeepClone();

Possiamo avere le seguenti tre implementazioni:

  1. Per serializzazione (il codice più breve)
  2. Per riflessione - 5 volte più veloce
  3. Tramite Expression Trees : 20 volte più veloce

Tutti i metodi collegati funzionano bene e sono stati profondamente testati.


la clonazione del codice utilizzando gli alberi delle espressioni che hai pubblicato codeproject.com/Articles/1111658/… , non riesce con le versioni più recenti del framework .Net con un'eccezione di sicurezza, l' operazione potrebbe destabilizzare il runtime , è fondamentalmente un'eccezione a causa dell'albero delle espressioni non valido, che viene utilizzato per generare il Func in fase di esecuzione, si prega di verificare se si dispone di qualche soluzione. In effetti ho riscontrato problemi solo con oggetti complessi con una gerarchia profonda, uno semplice viene facilmente copiato
Mrinal Kamboj,

1
L'implementazione di ExpressionTree sembra molto buona. Funziona anche con riferimenti circolari e membri privati. Nessun attributo necessario. La migliore risposta che ho trovato.
N73k,

La risposta migliore, ha funzionato molto bene, mi hai salvato la giornata
Adel Mourad, il

23

La risposta breve è ereditare dall'interfaccia ICloneable e quindi implementare la funzione .clone. Il clone dovrebbe eseguire una copia membro e eseguire una copia profonda su qualsiasi membro che lo richiede, quindi restituire l'oggetto risultante. Questa è un'operazione ricorsiva (richiede che tutti i membri della classe che si desidera clonare siano tipi di valore o implementino ICloneable e che i loro membri siano tipi di valore o implementino ICloneable e così via).

Per una spiegazione più dettagliata sulla clonazione usando ICloneable, leggi questo articolo .

La risposta lunga è "dipende". Come menzionato da altri, ICloneable non è supportato dai generici, richiede considerazioni speciali per i riferimenti di classe circolari ed è in realtà visto da alcuni come un "errore" in .NET Framework. Il metodo di serializzazione dipende dal fatto che i tuoi oggetti siano serializzabili, cosa che potrebbero non essere e che potresti non avere alcun controllo. C'è ancora molto dibattito nella comunità su quale sia la "migliore" pratica. In realtà, nessuna delle soluzioni è adatta a tutte le migliori pratiche per tutte le situazioni come ICloneable è stata inizialmente interpretata.

Consulta l'articolo di questo angolo dello sviluppatore per alcune altre opzioni (credito a Ian).


1
ICloneable non ha un'interfaccia generica, quindi non è consigliabile utilizzare tale interfaccia.
Karg,

La tua soluzione funziona finché non deve gestire riferimenti circolari, quindi le cose iniziano a complicarsi, è meglio provare a implementare la clonazione profonda utilizzando la serializzazione profonda.
Pop Catalin,

Sfortunatamente, non tutti gli oggetti sono nemmeno serializzabili, quindi non puoi nemmeno usare quel metodo. Il link di Ian è la risposta più completa finora.
Zach Burlingame,

19
  1. Fondamentalmente è necessario implementare l'interfaccia ICloneable e quindi realizzare la copia della struttura degli oggetti.
  2. Se si tratta di una copia approfondita di tutti i membri, è necessario assicurarsi (non in relazione alla soluzione scelta) che anche tutti i bambini siano clonabili.
  3. A volte è necessario essere consapevoli di alcune restrizioni durante questo processo, ad esempio se si copiano gli oggetti ORM la maggior parte dei framework consente solo un oggetto collegato alla sessione e NON DEVE creare cloni di questo oggetto, o se è possibile è necessario preoccuparsene sull'associazione di sessione di questi oggetti.

Saluti.


4
ICloneable non ha un'interfaccia generica, quindi non è consigliabile utilizzare tale interfaccia.
Karg,

Le risposte semplici e concise sono le migliori.
DavidGuaita,

17

EDIT: il progetto è sospeso

Se vuoi la vera clonazione su tipi sconosciuti puoi dare un'occhiata a fastclone .

Questa è una clonazione basata su espressioni che funziona circa 10 volte più velocemente della serializzazione binaria e mantiene la completa integrità del grafico a oggetti.

Ciò significa: se ci si riferisce più volte allo stesso oggetto nella propria gerarchia, al clone verrà anche fatta riferimento una sola istanza.

Non sono necessarie interfacce, attributi o altre modifiche agli oggetti clonati.


Questo sembra essere abbastanza utile
LuckyLikey,

È più facile iniziare a lavorare da un'istantanea del codice rispetto al sistema generale, in particolare a quello chiuso. È abbastanza comprensibile che nessuna libreria possa risolvere tutti i problemi con un solo colpo. Alcuni rilassamenti dovrebbero essere fatti.
TarmoPikaro,

1
Ho provato la tua soluzione e sembra funzionare bene, grazie! Penso che questa risposta debba essere votata più volte. L'implementazione manuale di ICloneable è noiosa e soggetta a errori, l'utilizzo della riflessione o della serializzazione è lento se le prestazioni sono importanti e è necessario copiare migliaia di oggetti in un breve periodo di tempo.
nightcoder

L'ho provato e non ha funzionato affatto per me. Genera un'eccezione MemberAccess.
Michael Brown,

Non funziona con le versioni più recenti di .NET ed è interrotto
Michael Sander,

14

Mantieni le cose semplici e usa AutoMapper come altri hanno già detto, è una semplice piccola libreria per mappare un oggetto su un altro ... Per copiare un oggetto su un altro con lo stesso tipo, tutto ciò di cui hai bisogno sono tre righe di codice:

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

L'oggetto target ora è una copia dell'oggetto sorgente. Non è abbastanza semplice? Crea un metodo di estensione da utilizzare ovunque nella tua soluzione:

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

Il metodo di estensione può essere utilizzato come segue:

MyType copy = source.Copy();

Fai attenzione con questo, si comporta davvero male. Ho finito per passare alla risposta johnc che è breve come questa e offre prestazioni molto migliori.
Agorilla,

1
Questo fa solo una copia superficiale.
N73k,

11

Ho pensato a questo per superare un .NET difetto di che doveva eseguire manualmente una copia approfondita dell'elenco <T>.

Io lo uso questo:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

E in un altro posto:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

Ho provato a trovare oneliner che lo fa, ma non è possibile, a causa della resa che non funziona all'interno di blocchi di metodi anonimi.

Meglio ancora, usa il cloner generico List <T>:

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

10

D. Perché dovrei scegliere questa risposta?

  • Scegli questa risposta se desideri la massima velocità di .NET.
  • Ignora questa risposta se desideri un metodo di clonazione davvero molto semplice.

In altre parole, vai con un'altra risposta a meno che tu non abbia un collo di bottiglia delle prestazioni che deve essere riparato e puoi provarlo con un profiler .

10 volte più veloce di altri metodi

Il seguente metodo per eseguire un clone profondo è:

  • 10 volte più veloce di tutto ciò che comporta la serializzazione / deserializzazione;
  • Abbastanza vicino alla velocità massima teorica di cui è capace .NET.

E il metodo ...

Per la massima velocità, puoi utilizzare Nested MemberwiseClone per eseguire una copia approfondita . È quasi la stessa velocità della copia di una struttura di valore ed è molto più veloce di (a) riflessione o (b) serializzazione (come descritto in altre risposte in questa pagina).

Si noti che se si utilizza Nested MemberwiseClone per una copia profonda , devi implementare manualmente uno ShallowCopy per ogni livello nidificato nella classe e un DeepCopy che chiama tutti i suddetti metodi ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo di seguito.

Ecco l'output del codice che mostra la differenza relativa delle prestazioni per 100.000 cloni:

  • 1,08 secondi per Nested MemberwiseClone su strutture nidificate
  • 4,77 secondi per Nested MemberwiseClone su classi nidificate
  • 39,93 secondi per serializzazione / deserializzazione

Usare Nested MemberwiseClone su una classe quasi alla stessa velocità della copia di una struttura e la copia di una struttura è quasi dannatamente vicino alla velocità massima teorica di cui è in grado di fare .NET.

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

Per capire come fare una copia profonda usando MemberwiseCopy, ecco il progetto demo che è stato usato per generare i tempi precedenti:

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

Quindi, chiama la demo da main:

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

Ancora una volta, nota che se usi Nested MemberwiseClone per una copia profonda , devi implementare manualmente uno ShallowCopy per ogni livello nidificato nella classe e un DeepCopy che chiama tutti i suddetti metodi ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo sopra.

Tipi di valore e tipi di riferimenti

Si noti che quando si tratta di clonare un oggetto, c'è una grande differenza tra una " struct " e una " class ":

  • Se hai una " struct ", è un tipo di valore in modo che tu possa semplicemente copiarlo e il contenuto verrà clonato (ma creerà solo un clone superficiale a meno che tu non usi le tecniche in questo post).
  • Se hai una " classe ", è un tipo di riferimento , quindi se la copi, tutto ciò che stai facendo è copiare il puntatore su di essa. Per creare un vero clone, devi essere più creativo e utilizzare le differenze tra tipi di valore e tipi di riferimenti che creano un'altra copia dell'oggetto originale in memoria.

Vedi le differenze tra tipi di valore e tipi di riferimenti .

Checksum per facilitare il debug

  • La clonazione errata di oggetti può portare a bug molto difficili da individuare. Nel codice di produzione, tendo a implementare un checksum per verificare che l'oggetto sia stato clonato correttamente e non sia stato danneggiato da un altro riferimento ad esso. Questo checksum può essere disattivato in modalità Rilascio.
  • Trovo questo metodo abbastanza utile: spesso vuoi solo clonare parti dell'oggetto, non l'intera cosa.

Davvero utile per disaccoppiare molti thread da molti altri thread

Un eccellente caso d'uso per questo codice è l'invio di cloni di una classe o struttura nidificata in una coda, per implementare il modello produttore / consumatore.

  • Possiamo avere uno (o più) thread che modificano una classe di loro proprietà, quindi inserire una copia completa di questa classe in a ConcurrentQueue .
  • Abbiamo quindi uno (o più) thread che estraggono copie di queste classi e le gestiscono.

In pratica funziona molto bene e ci consente di separare molti thread (i produttori) da uno o più thread (i consumatori).

E questo metodo è anche incredibilmente veloce: se usiamo le strutture nidificate, è 35 volte più veloce della serializzazione / deserializzazione delle classi nidificate e ci consente di sfruttare tutti i thread disponibili sulla macchina.

Aggiornare

Apparentemente, ExpressMapper è più veloce, se non più veloce, della codifica manuale come sopra. Potrei dover vedere come si confrontano con un profiler.


Se copi una struttura ottieni una copia superficiale, potresti aver ancora bisogno di un'implementazione specifica per una copia profonda.
Lasse V. Karlsen,

@Lasse V. Karlsen. Sì, hai assolutamente ragione, ho aggiornato la risposta per renderlo più chiaro. Questo metodo può essere utilizzato per eseguire copie profonde di strutture e classi. Puoi eseguire il codice demo di esempio incluso per mostrare come è fatto, ha un esempio di clonazione profonda di una struttura nidificata e un altro esempio di clonazione profonda di una classe nidificata.
Contango,

9

In generale, implementate l'interfaccia ICloneable e implementate Clone da soli. Gli oggetti C # hanno un metodo MemberwiseClone incorporato che esegue una copia superficiale che può aiutarti per tutte le primitive.

Per una copia profonda, non è possibile sapere come eseguirlo automaticamente.


ICloneable non ha un'interfaccia generica, quindi non è consigliabile utilizzare tale interfaccia.
Karg,

8

L'ho visto implementato anche attraverso la riflessione. Fondamentalmente c'era un metodo che avrebbe ripetuto i membri di un oggetto e li avrebbe copiati in modo appropriato nel nuovo oggetto. Quando ha raggiunto i tipi o le raccolte di riferimento, penso che abbia fatto una chiamata ricorsiva su se stesso. La riflessione è costosa, ma ha funzionato abbastanza bene.


8

Ecco un'implementazione di copia profonda:

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

2
Sembra un clone di membro perché non è a conoscenza delle proprietà del tipo di riferimento
sll

1
Se vuoi prestazioni incredibilmente veloci, non andare per questa implementazione: usa la riflessione, quindi non sarà così veloce. Al contrario, "l'ottimizzazione prematura è il male", quindi ignora il lato delle prestazioni fino a quando non hai eseguito un profiler.
Contango,

1
CreateInstanceOfType non è definito?
MonsterMMORPG,

Errore sull'interger: "Il metodo non statico richiede una destinazione."
Mr.B,

8

Poiché non sono riuscito a trovare un cloner che soddisfi tutti i miei requisiti in diversi progetti, ho creato un cloner profondo che può essere configurato e adattato a diverse strutture di codice invece di adattare il mio codice per soddisfare i requisiti dei cloner. Si ottiene aggiungendo annotazioni al codice che deve essere clonato o si lascia semplicemente il codice in quanto deve avere il comportamento predefinito. Utilizza la riflessione, digita le cache e si basa su una più rapida riflessione . Il processo di clonazione è molto veloce per un'enorme quantità di dati e un'alta gerarchia di oggetti (rispetto ad altri algoritmi basati su riflessione / serializzazione).

https://github.com/kalisohn/CloneBehave

Disponibile anche come pacchetto nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Ad esempio: Il seguente codice indicherà deepClone Address, ma eseguirà solo una copia superficiale del campo _currentJob.

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

7

Generatore di codici

Abbiamo visto molte idee dalla serializzazione all'implementazione manuale alla riflessione e voglio proporre un approccio totalmente diverso usando il generatore di codice CGbR . Il metodo di generazione del clone è efficiente in termini di memoria e CPU e quindi 300 volte più veloce del DataContractSerializer standard.

Tutto ciò che serve è una definizione di classe parziale con ICloneablee il generatore fa il resto:

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

Nota: l' ultima versione ha un controllo più nullo, ma li ho lasciati fuori per una migliore comprensione.


6

Mi piacciono così i 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));
        }
    }

Se hai più cose da copiare, aggiungile


6

Questo metodo ha risolto il problema per me:

private static MyObj DeepCopy(MyObj source)
        {

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

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

        }

Usalo in questo modo: MyObj a = DeepCopy(b);


6

Ecco una soluzione semplice e veloce che ha funzionato per me senza dover ricorrere alla serializzazione / deserializzazione.

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 : richiede

    using System.Linq;
    using System.Reflection;

È così che l'ho usato

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}

5

Segui questi passi:

  • Definire un ISelf<T>con una Selfproprietà di sola lettura che restituisce Te ICloneable<out T>, da cui deriva ISelf<T>e include un metodo T Clone().
  • Quindi definire un CloneBasetipo che implementa un protected virtual generic VirtualClonecasting MemberwiseCloneal tipo passato.
  • Ogni tipo derivato dovrebbe implementare VirtualClonechiamando il metodo clone di base e quindi facendo tutto il necessario per clonare correttamente quegli aspetti del tipo derivato che il metodo VirtualClone padre non ha ancora gestito.

Per la massima versatilità ereditaria, le classi che espongono la funzionalità di clonazione pubblica dovrebbero essere sealed, ma derivano da una classe di base che è altrimenti identica, tranne per la mancanza di clonazione. Anziché passare variabili del tipo clonabile esplicito, prendere un parametro di tipo ICloneable<theNonCloneableType>. Ciò consentirà una routine che prevede che un derivato clonabile Foofunzioni con un derivato clonabile di DerivedFoo, ma consentirà anche la creazione di derivati ​​non clonabili di Foo.


5

Penso che tu possa provare questo.

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

4

Ho creato una versione della risposta accettata che funziona con "[Serializable]" e "[DataContract]". È da un po 'che non lo scrivo, ma se ricordo bene [DataContract] avevo bisogno di un serializzatore diverso.

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

} 

4

Ok, ci sono alcuni esempi ovvi con la riflessione in questo post, MA la riflessione è generalmente lenta, fino a quando non inizi a memorizzarlo nella cache correttamente.

se lo memorizzi correttamente nella cache, allora clonerà in profondità l'oggetto 1000000 per 4,6 s (misurato da Watcher).

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

quindi prendi le proprietà memorizzate nella cache o aggiungi nuove al dizionario e le usi semplicemente

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

verifica il codice completo nel mio post in un'altra risposta

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


2
La chiamata prop.GetValue(...)è ancora riflessione e non può essere memorizzata nella cache. In un albero delle espressioni è però compilato, molto più velocemente
Tseng,

4

Poiché quasi tutte le risposte a questa domanda sono state insoddisfacenti o chiaramente non funzionano nella mia situazione, ho creato AnyClone che è interamente implementato con la riflessione e ha risolto tutti i bisogni qui. Non ero in grado di far funzionare la serializzazione in uno scenario complicato con una struttura complessa ed IClonableè tutt'altro che ideale - in realtà non dovrebbe nemmeno essere necessario.

Standard ignorare gli attributi sono supportate utilizzando [IgnoreDataMember], [NonSerialized]. Supporta raccolte complesse, proprietà senza setter, campi di sola lettura ecc.

Spero che aiuti qualcun altro là fuori che ha incontrato gli stessi problemi che ho fatto.


4

Disclaimer: sono l'autore del pacchetto citato.

Sono rimasto sorpreso dal fatto che le risposte migliori a questa domanda nel 2019 utilizzino ancora la serializzazione o la riflessione.

La serializzazione è limitante (richiede attributi, costruttori specifici, ecc.) Ed è molto lenta

BinaryFormatterrichiede l' Serializableattributo, JsonConverterrichiede un costruttore o attributi senza parametri, né gestisce molto bene i campi di sola lettura o le interfacce ed entrambi sono 10-30 volte più lenti del necessario.

Alberi delle espressioni

È possibile invece utilizzare Expression Trees o Reflection.Emit per generare il codice di clonazione una sola volta, quindi utilizzare quel codice compilato invece di riflettere lentamente o serializzare.

Avendo riscontrato il problema da solo e non vedendo alcuna soluzione soddisfacente, ho deciso di creare un pacchetto che fa proprio questo e funziona con ogni tipo ed è quasi veloce come un codice scritto personalizzato .

Puoi trovare il progetto su GitHub: https://github.com/marcelltoth/ObjectCloner

uso

Puoi installarlo da NuGet. Prendi il ObjectClonerpacchetto e usalo come:

var clone = ObjectCloner.DeepClone(original);

o se non ti dispiace inquinare anche il tuo tipo di oggetto con le estensioni ObjectCloner.Extensions, scrivi e scrivi:

var clone = original.DeepClone();

Prestazione

Un semplice parametro di clonazione di una gerarchia di classi ha mostrato prestazioni ~ 3 volte più veloci rispetto all'utilizzo di Reflection, ~ 12 volte più veloci rispetto alla serializzazione Newtonsoft.Json e ~ 36 volte più veloci di quelle altamente suggerite BinaryFormatter.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.