c# clone object - Oggetti di clonazione profonda





15 Answers

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);
    }
}
deep copy vb

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?




Dopo molte letture su molte delle opzioni collegate qui, e possibili soluzioni per questo problema, credo che tutte le opzioni siano sintetizzate abbastanza bene al link di Ian P (tutte le altre opzioni sono variazioni di quelle) e la migliore soluzione è fornita da agiledeveloper.com/articles/cloning072002.htm 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 ICloneable (cioè manualmente)

agiledeveloper.com/articles/cloning072002.htm .

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, l'uso del clone come è fornito in C # (e Java) può essere piuttosto problematico. È meglio fornire un costruttore di copia protetto (non pubblico) e invocarlo dal metodo clone. Questo ci dà la possibilità di delegare il compito di creare un oggetto a un'istanza di una classe stessa, fornendo così estensibilità e anche, creando in sicurezza gli oggetti usando il costruttore di copia protetta.

Speriamo che questa implementazione possa chiarire le cose:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    …
}

Ora considera che una classe deriva da Person.

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

Puoi provare a eseguire il seguente codice:

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

L'output prodotto sarà:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

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




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

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}



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

MyObject oldObj; // The existing object to clone

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

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

vedere la soluzione di clonazione profonda valueinjecter su CodePlex .




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 lunga risposta è "dipende". Come menzionato da altri, ICloneable non è supportato dai generici, richiede considerazioni speciali per i riferimenti alle classi circolari ed è visto da alcuni come un "errore" in .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 pratica "migliore". In realtà, nessuna delle soluzioni è la misura unica, adatta a tutte le migliori pratiche per tutte le situazioni in cui ICloneable è stato inizialmente interpretato.

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




  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 limitazioni 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.




Mantieni le cose semplici e usa AutoMapper come già detto, è una semplice piccola libreria per mappare un oggetto con un altro ... Per copiare un oggetto con un altro dello stesso tipo, tutto ciò che ti 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();



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.




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



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. Utilizza 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



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




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



Penso che puoi provarlo.

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



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.




Se il tuo albero degli oggetti è serializzabile potresti usare anche qualcosa del genere

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 è piuttosto 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.




Related


Tags

c#   .net   clone