Question Objets de clonage profond


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?


1827
2017-09-17 00:06


origine


Réponses:


Alors que la pratique standard consiste à mettre en ICloneable interface (décrite ici, donc je ne vais pas régurgiter), voici un joli copieur d'objet clone profond que j'ai trouvé sur Le projet de code il y a un moment et l'a incorporé dans nos affaires.

Comme mentionné ailleurs, cela nécessite que vos objets soient sérialisables.

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'idée est qu'elle sérialise votre objet et le désérialise ensuite en un objet frais. L'avantage est que vous n'avez pas à vous préoccuper de tout cloner quand un objet devient trop complexe.

Et avec l'utilisation de méthodes d'extension (également à partir de la source référencée à l'origine):

Dans le cas où vous préférez utiliser la nouvelle méthodes d'extension de C # 3.0, changez la méthode pour avoir la signature suivante:

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

Maintenant, l'appel de méthode devient simplement objectBeingCloned.Clone();.

MODIFIER (10 janvier 2015) J'ai pensé que je reviendrais sur ce point, pour mentionner que j'ai récemment commencé à utiliser (Newtonsoft) Json pour le faire, devrait être plus léger, et évite le surcoût des tags [Serializable]. (NB @atconway a indiqué dans les commentaires que les membres privés ne sont pas clonés en utilisant la méthode JSON)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

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

1466
2018-04-03 13:31



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

181
2017-09-17 01:12



La raison de ne pas utiliser ICloneable est ne pas parce qu'il n'a pas d'interface générique. La raison de ne pas l'utiliser est parce que c'est vague. Il ne précise pas si vous obtenez une copie superficielle ou profonde; C'est à la discrétion de l'exécutant.

Oui, MemberwiseClone fait une copie superficielle, mais le contraire de MemberwiseClone n'est pas Clone; ce serait, peut-être, DeepClone, qui n'existe pas. Lorsque vous utilisez un objet via son interface ICloneable, vous ne pouvez pas savoir quel type de clonage l'objet sous-jacent effectue. (Et les commentaires XML ne le rendront pas clair, car vous obtiendrez les commentaires de l'interface plutôt que ceux de la méthode Clone de l'objet.)

Ce que je fais habituellement est simplement de faire un Copy méthode qui fait exactement ce que je veux.


146
2017-09-26 20:18



Après beaucoup de lecture sur de nombreuses options liées ici, et des solutions possibles à ce problème, je crois toutes les options sont assez bien résumées à Ian PLe lien (toutes les autres options sont des variantes de ceux-ci) et la meilleure solution est fournie par Pedro77Le lien sur la question commentaires.

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:

le article Fast Deep Copy par les arbres d'expression   a également une comparaison des performances du clonage par Serialization, Reflection et Expression Trees.

Pourquoi je choisis ICloneable (c'est-à-dire manuellement)

M. Venkat Subramaniam (lien redondant ici) explique en détail pourquoi.

Tous ses cercles d'article autour d'un exemple qui essaie d'être applicable dans la plupart des cas, en utilisant 3 objets: La 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 à un 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.


83
2017-09-17 00:13



Je préfère un constructeur de copie à un clone. L'intention est plus claire.


69
2018-03-16 11:38



Méthode d'extension simple pour copier toutes les propriétés publiques. Fonctionne pour tous les objets et ne fait pas exiger que la classe soit [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 ) } );
        }
    };
}

36
2017-12-02 17:39



J'ai eu des problèmes en utilisant ICloneable dans Silverlight, mais j'ai aimé l'idée de sérialisation, je peux séraliser XML, donc j'ai fait ceci:

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

28
2017-10-15 17:55



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

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.


26
2017-12-24 22:56



Je viens de créer CloneExtensions bibliothèque projet. Il effectue un clonage rapide et approfondi à l'aide d'opérations d'affectation simples générées par la compilation de code d'exécution d'Expression Tree.

Comment l'utiliser?

Au lieu d'écrire votre propre Clone ou Copy Les méthodes avec un ton d'assignations entre les champs et les propriétés font que le programme le fait par vous-même, en utilisant Expression Tree. GetClone<T>() La méthode marquée comme méthode d'extension vous permet simplement de l'appeler sur votre instance:

var newInstance = source.GetClone();

Vous pouvez choisir ce qui doit être copié de source à newInstance en utilisant CloningFlags enum:

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

Que peut-on cloner?

  • Primitif (int, uint, byte, double, char, etc.), connu immuable types (DateTime, TimeSpan, String) et les délégués (y compris Action, Func, etc.)
  • Nullable
  • T [] tableaux
  • Des classes et des structures personnalisées, y compris des classes et des structures génériques.

Les membres de la classe / structure suivants sont clonés en interne:

  • Valeurs des champs publics et non en lecture seule
  • Valeurs des propriétés publiques avec les accesseurs get et set
  • Éléments de collection pour les types implémentant ICollection

À quelle vitesse c'est?

La solution est plus rapide que la réflexion, car les informations des membres ne doivent être collectées qu'une seule fois, avant GetClone<T> est utilisé pour la première fois pour un type donné T.

C'est aussi plus rapide qu'une solution basée sur la sérialisation lorsque vous clonez plus d'une paire d'instances du même type T.

et plus...

En savoir plus sur les expressions générées sur Documentation.

Exemple de liste de débogage d'expression pour 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:
}

}

ce qui a le même sens que le suivant c # code:

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

N'est-il pas tout à fait comme si vous écriviez votre propre Clone méthode pour List<int>?


25
2017-09-17 00:14