Domanda

Voglio fare qualcosa di simile:

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

E quindi apportare le modifiche apportate al nuovo oggetto che non trovano riscontro nell'oggetto originale.

Non ho spesso bisogno di questa funzionalità, in modo che quando è stato necessario, ho fatto ricorso alla creazione di un nuovo oggetto e quindi la copia di ogni proprietà individuale, ma mi lascia sempre con la sensazione che non c'è un meglio o un modo più elegante di gestire la situazione.

Come posso clone o copia profonda di un oggetto in modo che l'oggetto clonato possono essere modificati, senza modifiche si riflettono nell'oggetto originale?

È stato utile?

Soluzione

Mentre la pratica standard per implementare il ICloneable interfaccia (descritto qui, e , pertanto, non ripetere a pappagallo), ecco un bel deep clone oggetto copiatrice ho trovato su Il Progetto Di Codice un po ' di tempo fa ed è incorporato nella nostra roba.

Come già detto altrove, si ha bisogno di oggetti per essere serializzabile.

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 l'oggetto e poi deserializza in un fresco oggetto.Il vantaggio è che non dovete preoccuparvi sulla clonazione di tutto, quando un oggetto diventa troppo complessa.

E con l'uso di metodi di estensione (anche dall'originariamente riferimento fonte):

Nel caso In cui si preferisce utilizzare il nuovo i metodi di estensione di C# 3.0, modificare il metodo di avere la seguente firma:

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

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

MODIFICA (10 gennaio 2015) ho Pensato di rivisitare questo, parlare di recente ho iniziato a usare (Newtonsoft) Json per fare questo, si dovrebbe essere più leggero, e di evitare il sovraccarico di [Serializable] tags.(NB @atconway ha fatto notare nei commenti che i membri privati non sono clonati utilizzando JSON metodo)

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

Altri suggerimenti

Volevo un clonatore di semplici oggetti, per lo più di primitive e le liste.Se il vostro oggetto è al di fuori della casella di JSON serializable, quindi questo metodo farà il trucco.Questo non richiede alcuna modifica o implementazione di interfacce clonato classe, 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);
    }
}

Il motivo di non usare ICloneable è non perché non dispone di una interfaccia generica. Il motivo non è perché è vago.Non è chiaro se si tratti superficiale o profonda copia;fino all'implementatore.

Sì, MemberwiseClone fa una copia superficiale, ma l'opposto di MemberwiseClone non è Clone;sarebbe, forse, DeepClone, che non esiste.Quando si utilizza un oggetto attraverso la sua ICloneable interfaccia, non è possibile sapere che tipo di clonazione del sottostante oggetto svolge.(E i commenti XML non mettere in chiaro, perché si otterrà l'interfaccia commenti, piuttosto che quelli in oggetto, metodo Clone.)

Quello che faccio di solito è sufficiente fare una Copy metodo che fa esattamente quello che voglio.

Dopo tanto leggere su molte delle opzioni collegati qui, e le possibili soluzioni per questo problema, credo tutte le opzioni sono riassunti abbastanza bene Ian P's link (tutte le altre opzioni sono variazioni di quelli) e la soluzione migliore è fornito dalla Pedro77's link sulla questione dei commenti.

Quindi mi limiterò a copiare parti rilevanti di quelle 2 riferimenti qui.In questo modo possiamo avere:

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

In primo luogo, queste sono le nostre opzioni:

Il articolo Veloce Copia Profonda dall'Espressione Alberi ha anche il confronto delle prestazioni di clonazione per la Serializzazione, di Riflessione e di Espressione Alberi.

Perché ho scelto ICloneable (cioèmanualmente)

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

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

Questa è la mia versione leggermente modificata della sua conclusione:

La copia di un oggetto, specificando New seguita dal nome della classe spesso porta a codice che non è estendibile.Utilizzando clone, l'applicazione di un prototipo di modello, è un modo migliore per raggiungere questo obiettivo.Tuttavia, l'utilizzo di clone in quanto è fornito in C# e Java) può essere molto problematica.È meglio fornire una protezione (non pubblici) costruttore di copia e di richiamare il metodo clone.Questo ci dà la possibilità di delegare l'attività di creazione di un oggetto a un'istanza di una classe, fornendo così l'estensibilità e anche, in modo sicuro la creazione di oggetti utilizzando l'protetto costruttore di copia.

Speriamo che questa implementazione può rendere le cose chiare:

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, considerare avere una classe derivare dalla 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();
    }
}

Si può provare a eseguire il codice riportato di seguito:

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 vogliamo tenere un conteggio del numero di oggetti, il clone, come è implementato qui la volontà di mantenere un corretto conteggio del numero di oggetti.

Io preferisco un costruttore di copia di un clone.L'intento è chiaro.

Semplice metodo di estensione per copiare tutte le proprietà pubbliche.Funziona per tutti gli oggetti e non richiedono classe [Serializable].Può essere prorogata per un altro livello 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 ) } );
        }
    };
}

Beh, ho avuto problemi utilizzando ICloneable in Silverlight, ma mi piaceva l'idea di seralization, posso seralize XML, così ho fatto questo:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}

Ho appena creato CloneExtensions libreria progetto.Esegue veloce, profondo clone utilizzando semplici le operazioni di assegnazione generato dalla struttura ad Albero dell'Espressione di runtime di compilazione del codice.

Come si usa?

Invece di scrivere il proprio Clone o Copy metodi, con un tono di incarichi tra i campi e le proprietà rendono il programma di fare per se stessi, usando l'Espressione Albero. GetClone<T>() metodo contrassegnato come metodo di estensione permette semplicemente di chiamata sul tuo esempio:

var newInstance = source.GetClone();

Si può scegliere ciò che deve essere copiato dal source per newInstance utilizzando CloningFlags enum:

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

Che cosa può essere clonato?

  • Primitivo (int, uint, byte, double, char, etc.), noto immutabile tipi (DateTime, Durata, Stringa) e delegati (tra cui Azione, Func, ecc)
  • Nullable
  • T[] array
  • Classi e strutture personalizzate, tra cui le classi generiche e le strutture.

Classe seguente/i membri struttura sono clonati internamente:

  • I valori di pubblico, non solo di lettura campi
  • I valori delle proprietà pubbliche con entrambi ottenere e impostare le funzioni di accesso
  • Elementi di raccolta per i tipi di esecuzione ICollection

Per quanto riguarda la velocità è?

La soluzione è più veloce di riflessione, in quanto i membri, l'informazione deve essere raccolto solo una volta, prima di GetClone<T> viene utilizzato per la prima volta per un determinato tipo di T.

E ' anche più veloce di serializzazione soluzione quando si clona quindi più coppia istanze dello stesso tipo T.

e di più...

Leggi di più su generati espressioni documentazione.

Esempio di espressione di debug elenco 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:
}

}

che cosa ha lo stesso significato come il 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 ti piacerebbe scrivere la tua Clone metodo per List<int>?

Se si sta già utilizzando una applicazione 3rd party come ValueInjecter o Automapper, si può fare qualcosa di simile a questo:

MyObject oldObj; // The existing object to clone

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

Utilizzando questo metodo non è necessario implementare ISerializable o ICloneable sui tuoi oggetti.Questo è comune con l'MVC/MVVM disegno, strumenti semplici come questo sono stati creati.

vedere il valueinjecter profondo soluzione per la clonazione su CodePlex.

La risposta breve è che si eredita dal ICloneable interfaccia e quindi implementare il .clone funzione.Clone deve fare una copia membro e di eseguire una copia completa su qualsiasi membro che richiede, per poi tornare l'oggetto risultante.Questo è un operazione ricorsiva ( richiede che tutti i membri della classe che si desidera clonare sono tipi di valore o implementare ICloneable e che i loro membri sono tipi di valore o implementare ICloneable, e così via).

Per una spiegazione più dettagliata sulla Clonazione mediante ICloneable, check-out questo articolo.

Il lungo la risposta è "dipende".Come detto da altri, ICloneable non è supportato da farmaci generici, richiede considerazioni particolari per circolare riferimenti alla classe, e viene vista da alcuni come un "sbaglio" nell' .NET Framework.La serializzazione del metodo dipende da i tuoi oggetti serializzabili, che non può essere e si può avere alcun controllo su.C'è ancora molto dibattito nella comunità, che è la "migliore" in pratica.In realtà, nessuna delle soluzioni one-size fits tutte le migliori prassi per tutte le situazioni come ICloneable è stato originariamente interpretato da.

Vedere questo Angolo dello sviluppatore articolo per alcune opzioni in più (di credito, di Ian).

Il migliore è quello di implementare un metodo di estensione come

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

e poi usare ovunque, in soluzione,

var copy = anyObject.DeepClone();

Siamo in grado di avere le tre seguenti implementazioni:

  1. Dalla Serializzazione (il più breve), del codice)
  2. La Riflessione - 5 volte più veloce
  3. Dall'Espressione Alberi - 20 di più

Tutto è legato, metodi di lavoro e sono state collaudate.

  1. Fondamentalmente, è necessario implementare ICloneable interfaccia e quindi realizzare la struttura dell'oggetto copia.
  2. Se si tratta di copia completa di tutti i membri, è necessario assicurarsi che (non relative a soluzione scelta) che tutti i bambini sono clonabile così.
  3. A volte è necessario essere consapevoli di alcune restrizioni durante questo processo, per esempio, se si copia l'ORM oggetti la maggior parte dei quadri di consentire un solo oggetto collegato di sessione e NON si DEVE fare cloni di questo oggetto, o se è possibile è necessario preoccuparsi sessione di collegamento di questi oggetti.

Ciao.

Se si desidera che la vera clonazione di tipo sconosciuto e si può dare un'occhiata a fastclone.

Che l'espressione in base clonazione di lavoro di circa 10 volte più veloce di serializzazione binaria e il mantenimento completa di oggetti grafici integrità.

Che significa:se si fa riferimento più volte per lo stesso oggetto nella gerarchia, il clone, anche una singola istanza trattandosi di riferimento.

Non c'è bisogno di interfacce, attributi o qualsiasi altra modifica agli oggetti che vengono clonati.

Mantenere le cose semplici e di uso AutoMapper come gli altri citati, è un piccolo, semplice libreria per mappare un oggetto a un altro...Per copiare un oggetto a un altro con lo stesso tipo, tutto che abbiate bisogno di è di 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 per usare 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 di diventare una riga:

MyType copy = source.Copy();

Mi si avvicinò con questo per superare un .NET difetto di dover manualmente copia profonda Elenco<T>.

Io uso questo:

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

E in altro luogo:

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

Ho provato a venire con oneliner che fa questo, ma non è possibile, a causa della resa che non lavorano all'interno metodo anonimo blocchi.

Meglio ancora, utilizzare Elenco generico<T> cloner:

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

D.Perché ho scelto questa risposta?

  • Scegliere questa risposta, se si desidera che la velocità più veloce .NET è in grado di.
  • Ignorare questa risposta se vuoi davvero un facile metodo di clonazione.

In altre parole, andare con un'altra risposta, a meno che non si dispone di un collo di bottiglia che ha bisogno di fissaggio, e si può dimostrare con un profiler.

10 volte più veloce rispetto ad altri metodi

Il seguente metodo di esecuzione di una profonda clone è:

  • 10 volte più veloce di tutto ciò che riguarda la serializzazione/deserializzazione;
  • Maledettamente vicino alla velocità massima teorica .NET è in grado di.

E il metodo ...

Per la massima velocità, è possibile utilizzare Nidificato MemberwiseClone fare una copia profonda.La sua quasi la stessa velocità di copia di un valore struct, ed è molto più veloce (una riflessione) o (b) la serializzazione (come descritto in altre risposte a questa pagina).

Nota che se si utilizza Nidificato MemberwiseClone per una copia profonda, è necessario implementare un ShallowCopy per ogni nidificati livello in classe, e un DeepCopy che chiama in causa tutti, ha detto ShallowCopy metodi per creare un clone completo.Questo è semplice:solo poche righe in totale, vedere la demo di codice riportato di seguito.

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

  • 1.08 secondi per Nidificati MemberwiseClone su strutture nidificate
  • 4.77 secondi per Nidificati MemberwiseClone su classi nidificate
  • 39.93 secondi per la Serializzazione/Deserializzazione

Utilizzando Nidificati MemberwiseClone in una classe quasi veloce come la copia di una struttura, e la copia di una struct è maledettamente vicino alla velocità massima teorica .NET è in grado di.

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 utilizzando MemberwiseCopy, qui è la versione demo del progetto che è stato utilizzato per generare i tempi di cui 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;
    }
}

Poi, chiamata la demo dal 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();
}

Di nuovo, si noti che se si utilizza Nidificato MemberwiseClone per una copia profonda, è necessario implementare un ShallowCopy per ogni nidificati livello in classe, e un DeepCopy che chiama in causa tutti, ha detto ShallowCopy metodi per creare un clone completo.Questo è semplice:solo poche righe in totale, vedere il codice demo di cui sopra.

Tipi di valori di vsRiferimenti Tipi

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

  • Se si dispone di un "struct"è un tipo di valore così si può semplicemente copiare, e il contenuto verrà clonato (ma lo farà solo un superficiale clone a meno che non si utilizza le tecniche in questo post).
  • Se si dispone di un "classe"è un tipo di riferimento, così se lo si copia, si copia il puntatore ad esso.Per creare un vero e proprio clone, devi essere più creativi e utilizzare le differenze tra i tipi di valore e riferimenti tipi che crea una copia dell'oggetto originale in memoria.

Vedere le differenze tra i tipi di valore e riferimenti tipi.

Checksum per il debug

  • Clonazione di oggetti in modo non corretto può portare a molto difficile-to-pin-down bug.Nel codice di produzione, tendo a implementare un checksum per controllare che l'oggetto è stato clonato correttamente, e non è stato danneggiato da un altro riferimento.Questo checksum può essere spento in modalità di Rilascio.
  • Trovo questo metodo molto utile:spesso, si desidera solo per clonare le parti dell'oggetto, non l'intera cosa.

Davvero utile per il disaccoppiamento, il numero di thread da molti altri thread

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

  • Siamo in grado di avere una (o più) thread modifica di una classe di cui sono proprietari, quindi spingendo una copia completa di questa classe in un ConcurrentQueue.
  • Abbiamo poi una (o più) thread tirando le copie di queste classi e trattare con loro.

Questo funziona molto bene, in pratica, e ci permette di disaccoppiare molti thread (i produttori) da uno o più thread (i consumatori).

E questo metodo è assolutamente troppo veloce:se utilizziamo strutture nidificate, è 35x più veloce di serializzazione/deserializzazione classi nidificate, e ci permette di usufruire di tutti i thread disponibili sulla macchina.

Aggiornamento

A quanto pare, ExpressMapper è veloce, se non più veloce, di mano di codifica come sopra.Ho potuto vedere come si confronta con un profiler.

In generale, è possibile implementare la ICloneable interfaccia e implementazione Clone di te stesso.Oggetti C# hanno un built-in MemberwiseClone metodo che esegue una copia superficiale che può aiutarvi per tutte le primitive.

Per una copia profonda, non c'è modo che si può sapere come automaticamente farlo.

L'ho visto implementato attraverso la riflessione.In pratica c'era un metodo che potrebbe scorrere i membri di un oggetto e in modo appropriato copiare per il nuovo oggetto.Quando ha raggiunto i tipi di riferimento o collezioni penso che ha fatto una chiamata ricorsiva su se stessa.La riflessione è costoso, ma ha funzionato abbastanza bene.

Qui è una copia profonda di attuazione:

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

Non riuscivo a trovare un cloner che soddisfa tutte le mie esigenze in diversi progetti, ho creato una profonda cloner che può essere configurato e adattato alle diverse strutture di codice, invece di adattare il mio codice per soddisfare le cloners requisiti.La sua ottenuta con l'aggiunta di annotazioni al codice che deve essere clonato o semplicemente lasciare il codice, come è di avere il comportamento di default.Utilizza la riflessione, il tipo di cache e si basa su fasterflect.Il processo di clonazione è molto veloce per una quantità enorme di dati e di alta gerarchia di oggetti (rispetto ad altri di riflessione/serializzazione a base di algoritmi).

https://github.com/kalisohn/CloneBehave

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

Per esempio:Il codice riportato di seguito verrà deepClone Indirizzo, ma solo per eseguire una copia superficiale del _currentJob campo.

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

Questo metodo è stato 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);

        }

Usarlo come questo: MyObj a = DeepCopy(b);

Mi piace Copyconstructors così:

    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 si hanno più cose da copiare aggiungerli

Generatore Di Codice

Abbiamo visto un sacco di idee da serializzazione su manuale di attuazione per la riflessione e la voglio proporre un modo completamente diverso di approccio che utilizza il CGbR Generatore Di Codice.L'generare metodo clone è la memoria e la CPU efficiente e loro 300x più veloci, come il standard DataContractSerializer.

Tutto ciò che serve è una parziale definizione di classe 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: Ultima versione ha più controlli nulli, ma li ho lasciati fuori per una migliore comprensione.

Ecco una soluzione semplice e veloce che per me ha funzionato senza stabulazione su di 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;
    }
}

MODIFICA:richiede

    using System.Linq;
    using System.Reflection;

Ecco Come l'ho usato

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

Attenersi alla seguente procedura:

  • Definire un ISelf<T> con una sola lettura Self proprietà che restituisce T, e ICloneable<out T>, che deriva da ISelf<T> e include un metodo T Clone().
  • Quindi definire un CloneBase tipo che implementa un protected virtual generic VirtualClone casting MemberwiseClone per il tipo passato-in.
  • Ogni tipo derivato dovrebbe implementare VirtualClone chiamando la base del metodo clone e poi fare ciò che deve essere fatto correttamente clone di quelli che sono gli aspetti di tipo derivato che il genitore VirtualClone metodo non ancora trattati.

Per la massima eredità versatilità, classi, esporre in pubblico la clonazione di funzionalità che devono essere sealed, ma derivano da una classe base che è identico tranne che per la mancanza di clonazione.Piuttosto che passare le variabili di esplicita clonabile tipo, accetta un parametro di tipo ICloneable<theNonCloneableType>.Questo permetterà una routine che prevede un clonabile derivati di Foo lavorare con un clonabile derivati di DerivedFoo, ma anche consentire la creazione di non clonabile derivati di Foo.

Penso che si può provare questo.

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

Ho creato una versione di accettato la risposta che funziona sia con i '[Serializable] " e " [DataContract]'.È stato un po ' da quando l'ho scritto, ma se non ricordo male [DataContract] bisogno di un diverso serializzatore.

Richiede Sistema, Sistema.IO, di Sistema.Runtime.La Serializzazione Di Sistema.Runtime.La serializzazione.Formattatori.Binario, 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;
    }

} 

Per clonare l'oggetto di classe è possibile utilizzare l'Oggetto.MemberwiseClone metodo,

basta aggiungere questa funzione per la classe :

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

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

quindi per eseguire un profondo indipendente copiare, basta chiamare il DeepCopy metodo :

yourClass newLine = oldLine.DeepCopy();

spero che questo aiuta.

Ok, ci sono alcuni ovvi esempio con la riflessione in questo post, MA la riflessione è di solito lento, fino a quando si inizia a memorizzare correttamente.

se ti cache correttamente, che ti profonda clone 1000000 oggetto da 4,6 s (misurata dall'Osservatore).

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

quanto si prende di proprietà nella cache o aggiungere nuovo dizionario e li usa semplicemente

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

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

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

Se il vostro Albero di Oggetti è Serializeable si potrebbe anche usare qualcosa di simile a questo

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

di essere informato che questa Soluzione è abbastanza facile, ma non è così performante come altre soluzioni.

E assicurarsi che, se la Classe cresce, ci sarà ancora solo i campi clonato, che anche ottenere serializzato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top