it-swarm.it

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?

2042
NakedBrunch

Mentre la pratica standard è implementare l'interfaccia ICloneable (descritta qui , quindi non voglio rigurgitare), ecco una bella copiatrice di oggetti clone profonda che ho trovato su The Code Project qualche tempo fa e l'ho incorporata 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):

Se si preferisce utilizzare i nuovi metodi di estensione di C # 3.0, cambiare il metodo per ottenere la seguente firma:

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

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

EDIT (10 gennaio 2015) Ho pensato di rivisitare questo, per dire che recentemente ho iniziato a usare (Newtonsoft) Json per farlo, dovrebbe essere più leggero ed evita il sovraccarico dei tag [Serializable]. (NB @atconway ha indicato 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);
}
1600
johnc

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

Inoltre, puoi usare 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);
    }
}
244
craastad

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 crea una copia superficiale, ma l'opposto 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 creare un metodo Copy che fa esattamente quello che voglio.

163
Ryan Lundy

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 P 's link (tutte le altre opzioni sono variazioni di quelle) e la migliore soluzione è fornita da Pedro77 ' s 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:

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

Perché scelgo IConeable (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: Person , Brain e City . 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:

Copiare un oggetto specificando New seguito dal nome della classe porta spesso a un codice che non è estensibile. L'uso del clone, l'applicazione del modello prototipo, è un modo migliore per raggiungere questo obiettivo. Tuttavia, usare clone come è fornito in C # (e Java) può essere abbastanza 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 [email protected]
This is person with [email protected]
SkilledPerson: This is person with [email protected]
SkilledPerson: This is person with [email protected]

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

102
cregox

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

77
Nick

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

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
    //[email protected]
    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);
    }
}
30
Michael White

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

Come si usa?

Invece di scrivere i propri metodi Clone o Copy con un tono di assegnazioni tra campi e proprietà, fare in modo che il programma lo faccia da sé, usando Expression Tree. Il metodo GetClone<T>() contrassegnato come metodo di estensione consente di chiamarlo semplicemente sull'istanza:

var newInstance = source.GetClone();

Puoi scegliere cosa copiare da source a newInstance usando CloningFlags enum:

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

Cosa può essere clonato?

  • Primitive (int, uint, byte, double, char, ecc.), Tipi immutabili noti (DateTime, TimeSpan, String) e delegati (inclusi Action, Func, ecc.)
  • Nullable
  • T [] array
  • Classi e strutture personalizzate, incluse classi e strutture generiche.

I membri della classe/struct seguenti sono clonati internamente:

  • Valori dei campi pubblici, non di sola lettura
  • Valori delle proprietà pubbliche con get e set accessors
  • Elementi di raccolta per i tipi che implementano ICollection

Quanto è veloce?

La soluzione è più veloce della riflessione, perché le informazioni sui membri devono essere raccolte solo una volta, prima che GetClone<T> venga usato per la prima volta per il tipo dato T.

È anche più veloce della soluzione basata sulla serializzazione quando cloni di più quindi accoppia le istanze dello stesso tipo T.

e altro ancora ...

Maggiori informazioni sulle espressioni generate su 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 è proprio come scriverebbe il proprio Clone metodo per List<int>?

27
MarcinJuraszek

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.

vedi la soluzione di clonazione profonda valueinjecter su CodePlex .

26
Michael Cox

La risposta breve è ereditata dall'interfaccia ICloneable e quindi implementa la funzione .clone. Il clone deve eseguire una copia membro ed 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 o tipi di valore o implementino ICloneable e che i loro membri siano o tipi di valore o implementino ICloneable e così via).

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

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

Vedi questo articolo sull'angolo dello sviluppatore per alcune altre opzioni (credito a Ian).

20
Zach Burlingame

La cosa migliore è implementare un metodo di estensione come

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

e quindi utilizzarlo ovunque nella soluzione

var copy = anyObject.DeepClone();

Possiamo avere le seguenti tre implementazioni:

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

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

19
frakon
  1. Fondamentalmente è necessario implementare l'interfaccia ICloneable e quindi realizzare la copia della struttura degli oggetti.
  2. Se è una copia profonda di tutti i membri, è necessario assicurare (non riferendosi alla soluzione che si sceglie) che tutti i bambini siano clonabili.
  3. A volte è necessario essere consapevoli di alcune restrizioni durante questo processo, ad esempio se copi gli oggetti ORM la maggior parte dei framework consente solo un oggetto collegato alla sessione e NON DEVE fare cloni di questo oggetto, o se è possibile che tu debba preoccuparti riguardo al collegamento di sessione di questi oggetti.

Saluti.

16
dimarzionist

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

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

Ciò significa: se ti riferisci più volte allo stesso oggetto nella tua gerarchia, il clone avrà anche una singola istanza referenziata.

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

15
Michael Sander

Mantieni le cose semplici e usa AutoMapper come menzionato da altri, è una semplice piccola libreria per mappare un oggetto a un altro ... Per copiare un oggetto su un altro con il stesso tipo, tutto ciò che serve sono tre righe di codice:

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

L'oggetto di destinazione è ora una copia dell'oggetto di origine. Non abbastanza semplice? Creare un metodo di estensione da utilizzare ovunque nella 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;
}

Utilizzando il metodo di estensione, le tre linee diventano una riga:

MyType copy = source.Copy();
11
Stacked

Mi è venuto in mente questo per superare una . NET mancanza di dover copiare manualmente la lista di tipo <T>.

Io 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 creare oneliner che esegue questa operazione, ma non è possibile, a causa del rendimento che non funziona all'interno di blocchi di metodi anonimi.

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

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 vuoi un metodo di clonazione davvero facile.

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

10 volte più veloce rispetto ad altri metodi

Il seguente metodo per eseguire un clone profondo è:

  • 10 volte più veloce di qualsiasi cosa che implichi serializzazione/deserializzazione;
  • Abbastanza dannatamente vicino alla massima velocità teorica di cui .NET è capace.

E il metodo ...

Per la massima velocità, puoi usare Nested MemberwiseClone per fare una copia profonda . Ha quasi la stessa velocità con cui copia una struct value, ed è molto più veloce della (a) reflection o (b) serializzazione (come descritto in altre risposte in questa pagina).

Nota che se usi Nested MemberwiseClone per una copia profonda , devi implementare manualmente un ShallowCopy per ogni livello annidato nella classe e un DeepCopy che chiama tutti i suddetti metodi di ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo qui sotto.

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

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

Usando Nested MemberwiseClone su una classe quasi veloce come copiare una struct, e copiare una struct è dannatamente vicino alla velocità massima teorica di cui .NET è capace.

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 approfondita usando MemberwiseCopy, ecco il progetto dimostrativo che è stato usato per generare le volte sopra:

// 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 dal principale:

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 una ShallowCopy per ogni livello annidato in la classe e una DeepCopy che chiama tutti i suddetti metodi di ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo sopra.

Tipi di valore e tipi di referenze

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

  • Se hai una " struct ", è un tipo di valore così puoi semplicemente copiarlo, e il contenuto sarà clonato ( ma farà solo un clone superficiale a meno che non usi le tecniche in questo post).
  • Se hai una " classe ", è un tipo di riferimento , quindi se lo copi, tutto quello che stai facendo è copiare puntatore ad esso. Per creare un vero clone, devi essere più creativo e usare le differenze tra i tipi di valore e i tipi di riferimenti che crea un'altra copia dell'oggetto originale nella memoria .

Vedi differenze tra tipi di valore e tipi di riferimenti .

Checksum per aiutare nel debugging

  • La clonazione non corretta degli oggetti può portare a bug molto difficili da decifrare. Nel codice di produzione, tendo ad 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à di rilascio.
  • Trovo abbastanza utile questo metodo: spesso, vuoi solo clonare parti dell'oggetto, non l'intera cosa.

Davvero utile per disaccoppiare molti thread da molti altri thread

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

  • Possiamo avere uno (o più) thread che modificano una classe che possiedono, quindi spingere una copia completa di questa classe in un ConcurrentQueue.
  • Abbiamo quindi uno (o più) thread che tirano fuori copie di queste classi e le gestiscono.

Ciò funziona molto bene nella pratica e ci consente di disaccoppiare molti thread (i produttori) da uno o più thread (i consumatori).

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

Aggiornare

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

8
Contango

L'ho visto implementato anche attraverso la riflessione. Fondamentalmente c'era un metodo che avrebbe iterato attraverso i membri di un oggetto e copiato appropriatamente nel nuovo oggetto. Quando ha raggiunto tipi di riferimento o raccolte, penso che abbia fatto una chiamata ricorsiva su se stessa. La riflessione è costosa, ma ha funzionato abbastanza bene.

7
xr280xr

Non riuscendo a trovare un cloner che soddisfi tutte le mie esigenze in diversi progetti, ho creato un deep cloner che può essere configurato e adattato a diverse strutture di codice invece di adattare il mio codice per soddisfare i requisiti dei cloners. Si ottiene aggiungendo annotazioni al codice che deve essere clonato o lasciare semplicemente il codice così com'è per avere il comportamento predefinito. Usa la riflessione, digita le cache e si basa su fasterflect . 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 sarà 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
kalisohn

In generale, si implementa l'interfaccia ICloneable e si implementa Clone autonomamente. Gli oggetti C # hanno un metodo MemberwiseClone incorporato che esegue una copia superficiale che può aiutarti per tutti i primitivi.

Per una copia profonda, non c'è modo di sapere come farlo automaticamente.

7
HappyDude

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

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
JerryGoyal

Mi piacciono i Copyconstructors in questo modo:

    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 aggiungili

5
LuckyLikey

Generatore di codici

Abbiamo visto molte idee dalla serializzazione sull'implementazione manuale alla riflessione e voglio proporre un approccio completamente diverso usando il CGbR Code Generator . Il metodo di generazione dei cloni è efficiente in termini di memoria e CPU e quindi 300 volte più veloce del DataContractSerializer standard.

Tutto ciò di cui hai bisogno è una definizione di classe parziale con ICloneable e 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 più assegni nulli, ma li ho lasciati fuori per una migliore comprensione.

5
Toxantron

Ecco una soluzione rapida e semplice che ha funzionato per me senza inoltro su 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;

Ecco come l'ho usato

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

Penso che puoi provarlo.

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

Segui questi passi:

  • Definisci un ISelf<T> con una proprietà Self di sola lettura che restituisce T e ICloneable<out T>, che deriva da ISelf<T> e include un metodo T Clone().
  • Quindi definisci un tipo CloneBase che implementa un protected virtual generic VirtualClone casting MemberwiseClone al tipo passato.
  • Ogni tipo derivato dovrebbe implementare VirtualClone chiamando il metodo clone di base e quindi facendo tutto il necessario per clonare correttamente quegli aspetti del tipo derivato che il metodo padre VirtualClone non ha ancora gestito.

Per la massima versatilità di ereditarietà, le classi che espongono la funzionalità di clonazione pubblica dovrebbero essere sealed, ma derivano da una classe di base che è altrimenti identica tranne che per la mancanza di clonazione. Piuttosto che passare le variabili del tipo clonable esplicito, prendi un parametro di tipo ICloneable<theNonCloneableType>. Ciò consentirà una routine che si aspetta che un derivato clonabile di Foo funzioni con una derivata clonabile di DerivedFoo, ma consenta anche la creazione di derivati ​​non clonabili di Foo.

4
supercat

Ho creato una versione della risposta accettata che funziona con '[Serializable]' e '[DataContract]'. È passato un po 'di tempo da quando l'ho scritto, ma se ricordo bene [DataContract] avevo bisogno di un serializzatore diverso.

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

} 
3
Jeroen Ritmeijer

Se il tuo albero degli oggetti è serializzabile potresti usare anche qualcosa di simile

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

essere informati che questa soluzione è abbastanza semplice, ma non è così performante come potrebbero esserlo altre soluzioni.

E sii sicuro che se la classe cresce, ci saranno ancora solo quei campi clonati, che verranno anche serializzati.

3
LuckyLikey

Per clonare il tuo oggetto di classe puoi usare il metodo Object.MemberwiseClone,

aggiungi questa funzione alla tua classe:

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

quindi per eseguire una copia indipendente, basta chiamare il metodo DeepCopy:

yourClass newLine = oldLine.DeepCopy();

spero che questo ti aiuti.

3
Chtiwi Malek

Ok, ci sono alcuni esempi ovvi di riflessione in questo post, ma la riflessione è solitamente lenta, finché non si inizia a memorizzarla correttamente.

se lo memorizzi correttamente, allora clonerà in profondità 1000000 oggetti per 4,6 secondi (misurati da Watcher).

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

di quanto si prendono le proprietà memorizzate nella cache o aggiungere nuovi al dizionario e usarli semplicemente

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

controllo completo del codice nel mio post in un'altra risposta

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

3
Roma Borodov

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

Gli attributi standard ignorati sono supportati usando [IgnoreDataMember], [NonSerialized]. Supporta raccolte complesse, proprietà senza setter, campi readonly, ecc.

Spero che aiuti qualcun altro là fuori che si è imbattuto negli stessi problemi che ho avuto.

2
Michael Brown

Quando si utilizza Marc Gravells protobuf-net come serializzatore, la risposta accettata richiede alcune lievi modifiche, poiché l'oggetto da copiare non verrà attribuito con [Serializable] e, pertanto, non è serializzabile e il metodo Clone genererà un'eccezione.
L'ho modificato per funzionare con protobuf-net:

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    IFormatter formatter = ProtoBuf.Serializer.CreateFormatter<T>();
    using (Stream stream = new MemoryStream())
    {
        formatter.Serialize(stream, source);
        stream.Seek(0, SeekOrigin.Begin);
        return (T)formatter.Deserialize(stream);
    }
}

Controlla la presenza di un attributo [ProtoContract] e usa il formattatore di protobufs per serializzare l'oggetto.

1
Basti M

Estensione C # che supporterà anche i tipi "not ISerializable ".

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

Uso

       var obj2 = obj1.DeepClone()
1
Sameera R.

È incredibile quanto sforzo si possa spendere con l'interfaccia IClonable, specialmente se si hanno gerarchie di classi pesanti. Anche MemberwiseClone funziona in qualche modo in modo strano - non clona esattamente nemmeno le normali tipologie di strutture di tipo List.

E, naturalmente, il dilemma più interessante per la serializzazione è la serializzazione dei riferimenti posteriori - ad es. gerarchie di classi in cui si hanno relazioni figlio-genitore. Dubito che il serializzatore binario sarà in grado di aiutarti in questo caso. (Si concluderà con ricorsive loop + overflow dello stack).

In qualche modo mi è piaciuta la soluzione proposta qui: Come si fa una copia profonda di un oggetto in .NET (C # in particolare)?

tuttavia - non supportava gli elenchi, aggiungeva quel supporto, prendeva in considerazione anche la ri-genitorialità. Per la regola genitore solo che ho fatto che quel campo o proprietà dovrebbe essere chiamato "genitore", allora sarà ignorato da DeepClone. Potresti decidere le tue regole per i riferimenti di ritorno - per le gerarchie ad albero potrebbe essere "sinistra/destra", ecc ...

Ecco tutto lo snippet di codice incluso il codice di test:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;


            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }


    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name="type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name="obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);

                for (int i = 0; i < array.Length; i++)
                    copied.SetValue(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child's parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}
1
TarmoPikaro

Ancora un'altra risposta JSON.NET. Questa versione funziona con classi che non implementano ISerializable.

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
    }

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}
1
Matthew Watson

Un mappatore esegue una copia profonda. Foreach member of you object crea un nuovo oggetto e assegna tutti i suoi valori. Funziona in modo ricorsivo su ciascun membro interno non primitivo.

Ti suggerisco uno dei più veloci, attualmente attivamente sviluppati. Suggerisco UltraMapper https://github.com/maurosampietro/UltraMapper

Pacchetti Nuget: https://www.nuget.org/packages/UltraMapper/

1
Mauro Sampietro

Gli approcci generici sono tutti tecnicamente validi, ma volevo semplicemente aggiungere una mia nota poiché raramente abbiamo realmente bisogno di una vera copia profonda, e mi oppongo fermamente all'utilizzo della copia generica profonda nelle attuali applicazioni aziendali poiché ciò lo rende così potreste avere molti luoghi in cui gli oggetti vengono copiati e quindi modificati esplicitamente, è facile perdersi.

Nella maggior parte delle situazioni di vita reale si desidera anche avere il controllo granulare sul processo di copia il più possibile poiché non si è solo accoppiati al framework di accesso ai dati, ma nella pratica gli oggetti di business copiati dovrebbero essere raramente uguali al 100%. Pensa ad un esempio di riferimento usato dall'ORM per identificare i riferimenti agli oggetti, una copia completa completa copierà anche questo id così mentre in memoria gli oggetti saranno diversi, non appena lo invierai all'archivio dati, si lamenterà, quindi dovrai è necessario modificare manualmente queste proprietà dopo la copia e, se l'oggetto cambia, è necessario regolarlo in tutti i punti che utilizzano la copia generica.

Espandendo su @cregox rispondi con ICloneable, che cos'è in realtà una copia profonda? È solo un oggetto appena assegnato sull'heap identico all'oggetto originale ma occupa uno spazio di memoria diverso, in quanto tale piuttosto che utilizzare una funzionalità generica di clonazione perché non creare semplicemente un nuovo oggetto?

Personalmente utilizzo l'idea di metodi di factory static sui miei oggetti di dominio.

Esempio:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

Se qualcuno guarda come può strutturare l'istanziazione dell'oggetto mantenendo il pieno controllo sul processo di copia, è una soluzione con cui ho avuto molto successo personalmente. Anche i costruttori protetti lo fanno, altri sviluppatori sono costretti a usare i metodi di fabbrica che forniscono un singolo punto di istanza dell'oggetto che incapsula la logica di costruzione all'interno dell'oggetto. È anche possibile sovraccaricare il metodo e disporre di diverse logiche di clonazione per punti diversi, se necessario.

0

Ho trovato un nuovo modo di farlo, cioè Emit.

Possiamo usare Emit per aggiungere l'IL all'app ed eseguirlo. Ma non penso che sia un buon modo per voglio perfezionare questo che scrivo la mia risposta.

L'Emit può vedere il documento ufficiale e Guida

Dovresti imparare qualche IL per leggere il codice. Scriverò il codice che può copiare la proprietà in classe.

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

Il codice può essere in copia profonda ma può copiare la proprietà. Se vuoi fare una copia profonda che puoi cambiarlo per l'IL è troppo difficile da non poterlo fare.

0
lindexi

che ne dici di riformulare semplicemente all'interno di un metodo che dovrebbe invocare fondamentalmente un costruttore di copie automatico

T t = new T();
T t2 = (T)t;  //eh something like that

        List<myclass> cloneum;
        public void SomeFuncB(ref List<myclass> _mylist)
        {
            cloneum = new List<myclass>();
            cloneum = (List < myclass >) _mylist;
            cloneum.Add(new myclass(3));
            _mylist = new List<myclass>();
        }

sembra funzionare per me

0
will_m

Questo copierà tutte le proprietà leggibili e scrivibili di un oggetto su un altro.

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

ed è così che lo usi:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

o per copiare tutto:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
0
Ylli Prifti