[c#] Oggetti di clonazione profonda



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

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?




I've seen it implemented through reflection as well. Basically there was a method that would iterate through the members of an object and appropriately copy them to the new object. When it reached reference types or collections I think it did a recursive call on itself. Reflection is expensive, but it worked pretty well.




I like Copyconstructors like that:

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

If you have more things to copy add them




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.




Mi è venuto in mente questo per superare un problema di .NET che deve copiare manualmente la lista <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;
}

I tried to come up with oneliner that does this, but it's not possible, due to yield not working inside anonymous method blocks.

Better still, use generic List<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();
        }
    }
}



If your Object Tree is Serializeable you could also use something like this

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

be informed that this Solution is pretty easy but it's not as performant as other solutions may be.

And be sure that if the Class grows, there will still be only those fields cloned, which also get serialized.




Here a solution fast and easy that worked for me without relaying on Serialization/Deserialization.

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

    using System.Linq;
    using System.Reflection;

That's How I used it

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



As I couldn't find a cloner that meets all my requirements in different projects, I created a deep cloner that can be configured and adapted to different code structures instead of adapting my code to meet the cloners requirements. Its achieved by adding annotations to the code that shall be cloned or you just leave the code as it is to have the default behaviour. It uses reflection, type caches and is based on fasterflect . The cloning process is very fast for a huge amount of data and a high object hierarchy (compared to other reflection/serialization based algorithms).

https://github.com/kalisohn/CloneBehave

Also available as a nuget package: https://www.nuget.org/packages/Clone.Behave/1.0.0

For example: The following code will deepClone Address, but only perform a shallow copy of the _currentJob field.

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



To clone your class object you can use the Object.MemberwiseClone method,

just add this function to your class :

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

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

then to perform a deep independant copy, just call the DeepCopy method :

yourClass newLine = oldLine.DeepCopy();

spero che questo ti aiuti.




Follow these steps:

  • Define an ISelf<T> with a read-only Self property that returns T , and ICloneable<out T> , which derives from ISelf<T> and includes a method T Clone() .
  • Then define a CloneBase type which implements a protected virtual generic VirtualClone casting MemberwiseClone to the passed-in type.
  • Each derived type should implement VirtualClone by calling the base clone method and then doing whatever needs to be done to properly clone those aspects of the derived type which the parent VirtualClone method hasn't yet handled.

For maximum inheritance versatility, classes exposing public cloning functionality should be sealed , but derive from a base class which is otherwise identical except for the lack of cloning. Rather than passing variables of the explicit clonable type, take a parameter of type ICloneable<theNonCloneableType> . This will allow a routine that expects a cloneable derivative of Foo to work with a cloneable derivative of DerivedFoo , but also allow the creation of non-cloneable derivatives of Foo .




Here is a deep copy implementation:

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



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 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. Con Reflection - 5 volte più veloce
  3. Da Expression Trees - 20 volte più veloce

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




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



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






Links



Tags

c# c#   .net .net   clone