[C#] Objets de clonage profond



Answers

Je voulais un cloner pour des objets très simples, principalement des primitives et des listes. Si votre objet est hors de la boîte JSON sérialisable, alors cette méthode fera l'affaire. Cela ne nécessite aucune modification ou implémentation d'interfaces sur la classe clonée, juste un sérialiseur JSON comme JSON.NET.

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

Je veux faire quelque chose comme:

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

Et puis apporter des modifications au nouvel objet qui ne sont pas reflétées dans l'objet d'origine.

Je n'ai pas souvent besoin de cette fonctionnalité, alors quand cela a été nécessaire, j'ai eu recours à la création d'un nouvel objet, puis j'ai copié chaque propriété individuellement, mais cela me laisse toujours le sentiment qu'il y a une meilleure ou plus élégante la situation.

Comment puis-je copier ou copier en profondeur un objet afin que l'objet cloné puisse être modifié sans que des modifications soient reflétées dans l'objet d'origine?




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

J'espère que cela t'aides.




Après beaucoup de lecture sur de nombreuses options liées ici, et des solutions possibles à ce problème, je crois que toutes les options sont très bien résumées au lien de Ian P (toutes les autres options sont des variantes de celles-ci) et la meilleure solution est fournie par agiledeveloper.com/articles/cloning072002.htm sur les commentaires de la question.

Je vais donc simplement copier les parties pertinentes de ces deux références ici. De cette façon, nous pouvons avoir:

La meilleure chose à faire pour cloner les objets en c dièse!

Tout d'abord, ce sont toutes nos options:

L' article Fast Deep Copy par Expression Trees a également une comparaison des performances du clonage par Serialization, Reflection et Expression Trees.

Pourquoi choisir IClonable (c'est-à-dire manuellement)

agiledeveloper.com/articles/cloning072002.htm .

Tout son article tourne autour d'un exemple qui essaie d'être applicable dans la plupart des cas, en utilisant 3 objets: Personne , Cerveau et Ville . Nous voulons cloner une personne qui aura son propre cerveau mais la même ville. Vous pouvez soit visualiser tous les problèmes des autres méthodes ci-dessus peut apporter ou lire l'article.

Voici ma version légèrement modifiée de sa conclusion:

Copier un objet en spécifiant New suivi du nom de la classe conduit souvent à du code qui n'est pas extensible. L'utilisation de clones, l'application du modèle de prototype, est un meilleur moyen d'y parvenir. Cependant, l'utilisation de clone tel qu'il est fourni en C # (et Java) peut aussi être très problématique. Il est préférable de fournir un constructeur de copie protégé (non public) et de l'appeler à partir de la méthode clone. Cela nous donne la possibilité de déléguer la tâche de création d'un objet à une instance d'une classe elle-même, fournissant ainsi l'extensibilité et également, en toute sécurité créer les objets en utilisant le constructeur de copie protégée.

J'espère que cette implémentation peut clarifier les choses:

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

Considérons maintenant avoir une classe dérivée de la personne.

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

Vous pouvez essayer d'exécuter le code suivant:

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

La sortie produite sera:

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

Observez que, si nous gardons un compte du nombre d'objets, le clone implémenté ici gardera un compte correct du nombre d'objets.




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



Le mieux est de mettre en place une méthode d'extension

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

puis l'utiliser partout dans la solution par

var copy = anyObject.DeepClone();

Nous pouvons avoir les trois implémentations suivantes:

  1. Par sérialisation (le code le plus court)
  2. Par réflexion - 5x plus rapide
  3. Par les arbres d'expression - 20x plus vite

Toutes les méthodes liées fonctionnent bien et ont été profondément testées.




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.




Si vous utilisez déjà une application tierce comme ValueInjecter ou Automapper , vous pouvez faire quelque chose comme ceci:

MyObject oldObj; // The existing object to clone

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

En utilisant cette méthode, vous n'avez pas à implémenter ISerializable ou ICloneable sur vos objets. C'est commun avec le pattern MVC / MVVM, donc des outils simples comme celui-ci ont été créés.

voir la solution de clonage profond valueinjecter sur CodePlex .




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




Méthode d'extension simple pour copier toutes les propriétés publiques. Fonctionne pour tous les objets et ne nécessite pas de classe [Serializable] . Peut être étendu pour un autre niveau d'accès.

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 réponse courte est que vous héritez de l'interface ICloneable, puis que vous implémentez la fonction .clone. Clone devrait faire une copie de membre et effectuer une copie profonde sur tout membre qui en a besoin, puis retourner l'objet résultant. Il s'agit d'une opération récursive (elle nécessite que tous les membres de la classe que vous voulez cloner soient des types de valeur ou implémentent ICloneable et que leurs membres soient des types de valeur ou implémentent ICloneable, et ainsi de suite).

Pour une explication plus détaillée sur le clonage en utilisant ICloneable, consultez cet article .

La réponse longue est "ça dépend". Comme mentionné par d'autres, ICloneable n'est pas supporté par les génériques, nécessite des considérations spéciales pour les références de classe circulaires, et est effectivement considéré par certains comme une "erreur" dans le .NET Framework. La méthode de sérialisation dépend de la sérialisation de vos objets, ce qui peut ne pas être le cas et vous n'avez aucun contrôle sur ces objets. Il y a encore beaucoup de débats dans la communauté sur ce qui est la «meilleure» pratique. En réalité, aucune des solutions ne constitue la meilleure pratique pour toutes les situations comme ICloneable a été interprété à l'origine.

Voir l'article de cet Developer's Corner pour quelques options supplémentaires (crédit à Ian).




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



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.




Suivez ces étapes:

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




Je suis venu avec cela pour surmonter un défaut. .NET ayant à copier manuellement la liste <T>.

J'utilise ceci:

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

Et à un autre endroit:

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

J'ai essayé de trouver un oneliner qui fait cela, mais ce n'est pas possible, car le rendement ne fonctionne pas dans les blocs de méthodes anonymes.

Mieux encore, utilisez le clonage List <T> générique:

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



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



Links