Question

Je veux faire quelque chose comme:

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

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

Je n’ai pas souvent besoin de cette fonctionnalité, c’est pourquoi j’ai eu recours à la création d’un nouvel objet, puis à la copie individuelle de chaque propriété, mais j’ai toujours l’impression qu’il existe un meilleur ou plus élégant manière de gérer la situation.

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

Était-ce utile?

La solution

La pratique habituelle consiste à implémenter le ICloneable interface (décrit ici , je ne vais donc pas régurgiter), voici un J'ai trouvé un Le projet de code

Comme mentionné précédemment, vos objets doivent être 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’il sérialise votre objet puis le désérialise en un nouvel objet. L'avantage est que vous n'avez pas à vous préoccuper du clonage de tout lorsqu'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):

Si vous préférez utiliser les nouvelles méthodes d'extension de C # 3.0, remplacez la méthode par avoir la signature suivante:

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

L’appel de méthode devient alors tout simplement objectBeingCloned.Clone();.

MODIFIER (10 janvier 2015) Bien que je revienne sur cette question, pour mentionner que j'ai récemment commencé à utiliser (Newtonsoft) Json pour ce faire, il a devrait être plus léger et éviter la surcharge de [ Serializable] tags. ( NB , @atconway a indiqué dans ses commentaires que les membres privés ne sont pas clonés à l'aide de 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);
}

Autres conseils

Je voulais un cloneur pour des objets très simples, composés principalement de primitives et de listes. Si votre objet est JSON sérialisable prêt à l'emploi, cette méthode fera l'affaire. Cela ne nécessite aucune modification ni implémentation d'interfaces sur la classe clonée, mais uniquement un sérialiseur JSON tel que JSON.NET.

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

De plus, vous pouvez utiliser cette méthode d'extension

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

La raison de ne pas utiliser ICloneable est pas car il ne possède pas d'interface générique. Si vous ne l'utilisez pas, c'est parce que c'est vague . Cela n'indique pas clairement si vous obtenez une copie superficielle ou profonde; cela dépend de l'implémenteur.

Oui, MemberwiseClone crée une copie superficielle, mais l'inverse de Clone n'est pas DeepClone; ce serait peut-être Copy, ce 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 seront pas clairs, car vous obtiendrez les commentaires d'interface plutôt que ceux de la méthode Clone de l'objet.)

Ce que je fais habituellement est simplement de créer une <=> méthode qui fait exactement ce que je veux.

Après avoir beaucoup lu sur les nombreuses options liées ici et sur les solutions possibles à ce problème, je pense toutes les options sont bien résumées sur le lien de Ian P (toutes les autres options sont des variantes de celles-ci) et la meilleure solution est fournie par < a href = "http://www.agiledeveloper.com/articles/cloning072002.htm" rel = "noreferrer"> Le lien de Pedro77 sur les commentaires de la question.

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

La meilleure chose à faire pour cloner des objets en c sharp!

D'abord et avant tout, ce sont toutes nos options:

Le article Fast Deep Copier par arbres d’expression permet également de comparer les performances du clonage par arborescence de sérialisation, de réflexion et d’expression.

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

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

Tous ses articles décrivent un exemple qui tente d’être applicable dans la plupart des cas, en utilisant 3 objets: Personne , Brain et Ville . Nous voulons cloner une personne qui aura son propre cerveau mais la même ville. Vous pouvez imaginer tous les problèmes que l’une des méthodes susmentionnées peut entraîner ou lire l’article.

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

  

La copie d'un objet en spécifiant New suivi du nom de la classe entraîne souvent un code non extensible. Utiliser clone, l’application du motif prototype, est un meilleur moyen d’y parvenir. Cependant, l’utilisation du clone tel qu’il est fourni en C # (et Java) peut également être assez problématique. Il est préférable de fournir un constructeur de copie protégé (non public) et de l'invoquer à partir de la méthode clone. Cela nous donne la possibilité de déléguer la tâche de créer un objet à une instance de classe elle-même, offrant ainsi une extensibilité et une création sécurisée des objets à l'aide du constructeur de copie protégée.

Espérons que cette implémentation puisse 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);
    }
    …
}

Maintenant consider ayant une classe dérive de 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();
    }
}

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

Notez que, si nous tenons un compte du nombre d'objets, le clone tel qu'implémenté ici conservera un compte correct du nombre d'objets.

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

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

Eh bien, je rencontrais des problèmes lors de l'utilisation de ICloneable dans Silverlight, mais j'aimais l'idée de la sérialisation. Je peux sérialiser XML. Je l'ai donc fait:

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

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

Comment l'utiliser?

Au lieu d’écrire vos propres méthodes Clone ou Copy avec une tonalité d’assignations entre les champs et les propriétés, le programme le fait pour vous-même, en utilisant Expression Tree. GetClone<T>() La méthode marquée comme extension vous permet de l'appeler simplement 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?

  • Primitive (int, uint, octet, double, caractère, etc.), connue comme étant immuable types (DateTime, TimeSpan, String) et des délégués (y compris Action, Func, etc.)
  • Nullable
  • T [] tableaux
  • Classes et structures personnalisées, y compris les classes et structures génériques.

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

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

À quelle vitesse?

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

C’est également plus rapide que la solution basée sur la sérialisation lorsque vous clonez plus de deux instances du même type List<int>.

et plus encore ...

En savoir plus sur les expressions générées dans la documentation .

Exemple de liste de débogage d'expression pour <=>:

.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:
}

}

Qu'est-ce qui a le même sens que le code c # suivant:

(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-ce pas tout à fait comme vous écrivez votre propre <=> méthode pour <=>?

Si vous utilisez déjà une application tierce telle que 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

Avec cette méthode, vous n'avez pas à implémenter ISerializable ou ICloneable sur vos objets. Ceci est courant avec le modèle MVC / MVVM, des outils simples comme celui-ci ont été créés.

voir le solution de clonage profond de valueinjecter sur CodePlex .

La réponse courte est que vous héritez de l'interface ICloneable, puis que vous implémentez la fonction .clone. Clone doit faire une copie dans le sens membre et effectuer une copie complète de tout membre qui en a besoin, puis renvoyer l'objet obtenu. 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 valeur ou implémentent ICloneable et que leurs membres soient des types valeur ou implémentent ICloneable, etc.).

Pour une explication plus détaillée sur le clonage à l'aide de ICloneable, consultez cet article .

La réponse longue est & "ça dépend &"; Comme mentionné par d'autres, ICloneable n'est pas pris en charge par les génériques, nécessite des considérations spéciales pour les références de classe circulaires et est en réalité considéré par certains comme un &" Erreur & "; dans le .NET Framework. La méthode de sérialisation dépend de la sérialisation de vos objets, qu'ils ne peuvent pas être et que vous ne pouvez pas contrôler. Il y a encore beaucoup de débats dans la communauté sur le & "Meilleur &"; entraine toi. En réalité, aucune des solutions proposées n'est la même pour toutes les meilleures pratiques dans toutes les situations telles que ICloneable avait été interprétée à l'origine.

Voir le article du coin du développeur pour quelques options supplémentaires (crédit à Ian).

Le mieux est d'implémenter une méthode d'extension comme

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

puis utilisez-le n’importe où dans la solution en

var copy = anyObject.DeepClone();

Nous pouvons avoir les trois implémentations suivantes:

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

Toutes les méthodes liées fonctionnent correctement et ont fait l'objet de tests approfondis.

  1. Fondamentalement, vous devez implémenter l'interface ICloneable puis réaliser la copie de la structure d'objet.
  2. S'il s'agit d'une copie complète de tous les membres, vous devez vous assurer (sans indiquer la solution que vous choisissez) que tous les enfants sont également clonables.
  3. Parfois, vous devez être conscient de certaines restrictions au cours de ce processus, par exemple si vous copiez les objets ORM, la plupart des frameworks n'autorisent qu'un seul objet attaché à la session et vous NE DEVEZ PAS faire de clonage de cet objet, ou s'il est possible de devez vous préoccuper de la connexion de session de ces objets.

A bientôt.

Si vous voulez un vrai clonage vers des types inconnus, vous pouvez jeter un oeil à fastclone .

Le clonage basé sur l'expression fonctionne environ 10 fois plus rapidement que la sérialisation binaire et maintient l'intégrité complète du graphe des objets.

Cela signifie que si vous faites référence plusieurs fois au même objet dans votre hiérarchie, une seule instance sera également référencée.

Il n'est pas nécessaire de disposer d'interfaces, d'attributs ou de toute autre modification des objets en cours de clonage.

Simplifiez-vous la vie et utilisez AutoMapper , comme d'autres l'ont mentionné, c'est une simple petite bibliothèque permettant de mapper un objet à un autre. . Pour copier un objet dans un autre du même type, il vous suffit de trois lignes de code:

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);

L'objet cible est maintenant une copie de l'objet source. Pas assez simple? Créez une méthode d'extension à utiliser partout dans votre solution:

public static T Copy<T>(this T source)
{
    T copy = default(T);
    Mapper.CreateMap<T, T>();
    copy = Mapper.Map<T, T>(source);
    return copy;
}

En utilisant la méthode d'extension, les trois lignes deviennent une ligne:

MyType copy = source.Copy();

J'ai proposé cette solution pour remédier à un problème de .NET , qui devait manuellement copier en profondeur 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 créer un oneliner qui le fait, mais ce n'est pas possible car le rendement ne fonctionne pas dans les blocs de méthodes anonymes.

Mieux encore, utilisez la liste générique < 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();
        }
    }
}

Q. Pourquoi devrais-je choisir cette réponse?

  • Choisissez cette réponse si vous voulez la vitesse la plus rapide .NET est capable de.
  • Ignorez cette réponse si vous voulez une méthode de clonage vraiment très simple.

En d'autres termes, donnez une autre réponse, sauf si vous rencontrez un goulet d'étranglement qui nécessite une correction, et vous pouvez prouver avec un profileur .

10 fois plus rapide que les autres méthodes

La méthode suivante pour effectuer un clone en profondeur est la suivante:

  • 10 fois plus rapide que tout ce qui implique la sérialisation / désérialisation;
  • C'est presque aussi rapide que la vitesse maximale théorique dont est capable NET.

Et la méthode ...

Pour une vitesse optimale, vous pouvez utiliser Nested MemberwiseClone pour effectuer une copie en profondeur . C'est presque la même vitesse que de copier une structure de valeur, et est beaucoup plus rapide que (a) la réflexion ou (b) la sérialisation (comme décrit dans d'autres réponses sur cette page).

Notez que si vous utilisez Nwise MemberwiseClone pour une copie en profondeur , vous devez implémenter manuellement une ShallowCopy pour chaque niveau imbriqué de la classe et une DeepCopy qui appelle tous les utilisateurs. a déclaré les méthodes ShallowCopy pour créer un clone complet. C’est simple: quelques lignes au total, voir le code de démonstration ci-dessous.

Voici la sortie du code indiquant la différence de performance relative pour 100 000 clones:

  • 1,08 seconde pour Nested MemberwiseClone sur des structures imbriquées
  • 4,77 secondes pour Nested MemberwiseClone sur des classes imbriquées
  • 39,93 secondes pour la sérialisation / désérialisation

Utiliser Nested MemberwiseClone sur une classe presque aussi rapidement que copier une structure, et copier une structure est sacrément proche de la vitesse maximale théorique dont est capable NET.

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

Pour comprendre comment effectuer une copie en profondeur à l'aide de MemberwiseCopy, voici le projet de démonstration utilisé pour générer les temps précédents:

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

Ensuite, appelez la démo à partir de 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();
}

Encore une fois, notez que si vous utilisez Nested MemberwiseClone pour une copie en profondeur , vous devez implémenter manuellement un ShallowCopy pour chaque niveau imbriqué de la classe et un DeepCopy qui appelle toutes les méthodes dites ShallowCopy pour créer un clone complet. C’est simple: quelques lignes au total, voir le code de démonstration ci-dessus.

Types de valeur et types de références

Notez que lorsqu'il s'agit de cloner un objet, il existe une grande différence entre un & " struct &"; et une & "; classe &":

  • Si vous avez une & "; structure &"; c'est un type de valeur afin que vous puissiez simplement la copier, le contenu sera cloné. (mais cela ne fera qu’un clone peu profond, sauf si vous utilisez les techniques décrites dans ce post).
  • Si vous avez une & "; classe &"; c'est un type de référence , si vous la copiez, vous ne faites que copier. le pointeur vers elle. Pour créer un vrai clone, vous devez être plus créatif et utiliser différences entre les types de valeur et les types de référence , ce qui crée une autre copie de l'objet d'origine en mémoire.

Voir différences entre les types de valeur et types de références .

Des sommes de contrôle pour faciliter le débogage

  • Le clonage incorrect d'objets peut conduire à des bogues très difficiles à cerner. Dans le code de production, j'ai tendance à mettre en œuvre une somme de contrôle pour vérifier que l'objet a été correctement cloné et qu'il n'a pas été corrompu par une autre référence. Cette somme de contrôle peut être désactivée en mode Libération.
  • Je trouve cette méthode très utile: souvent, vous ne voulez que cloner des parties de l'objet, nont la chose entière.

vraiment utile pour découpler de nombreux threads de nombreux autres threads

Un excellent cas d'utilisation de ce code consiste à insérer des clones d'une classe ou d'une structure imbriquée dans une file d'attente afin d'implémenter le modèle producteur / consommateur.

  • Nous pouvons avoir un (ou plusieurs) threads modifiant une classe dont ils sont propriétaires, puis poussant une copie complète de cette classe dans un ConcurrentQueue.
  • Nous avons ensuite un (ou plusieurs) threads qui extraient des copies de ces classes et les traitent.

Cela fonctionne extrêmement bien dans la pratique et nous permet de dissocier plusieurs threads (les producteurs) d’un ou plusieurs threads (les consommateurs).

Et cette méthode est également extrêmement rapide: si nous utilisons des structures imbriquées, elle est 35 fois plus rapide que la sérialisation / désérialisation des classes imbriquées et nous permet de tirer parti de tous les threads disponibles sur la machine.

Mettre à jour

Apparemment, ExpressMapper est aussi rapide, sinon plus, que le codage manuel tel que celui décrit ci-dessus. Je devrais peut-être voir comment ils se comparent à un profileur.

En général, vous implémentez l’interface configurable et implémentez vous-même Cloner. Les objets C # ont une méthode MemberwiseClone intégrée qui effectue une copie superficielle pouvant vous aider pour toutes les primitives.

Pour une copie en profondeur, il n’ya aucun moyen de savoir comment le faire automatiquement.

Je l’ai également vu mis en œuvre par la réflexion. Fondamentalement, il existait une méthode qui parcourait les membres d’un objet et les copiait de manière appropriée dans le nouvel objet. Quand il a atteint des types de référence ou des collections, je pense que cela s'est fait un appel récursif. La réflexion coûte cher, mais cela a plutôt bien fonctionné.

Voici une implémentation en copie profonde:

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

Comme je ne pouvais pas trouver un cloneur qui réponde à toutes mes exigences dans différents projets, j'ai créé un clonage profond qui peut être configuré et adapté à différentes structures de code au lieu d'adapter mon code aux exigences des cloneurs. Pour ce faire, vous devez ajouter des annotations au code à cloner ou laisser le code tel quel pour adopter le comportement par défaut. Il utilise la réflexion, les caches de type et est basé sur plus rapide . Le processus de clonage est très rapide pour une énorme quantité de données et une hiérarchie d'objets élevée (par rapport à d'autres algorithmes basés sur la réflexion / sérialisation).

https://github.com/kalisohn/CloneBehave

Egalement disponible en paquet nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Par exemple: le code suivant va deepClone Address, mais n’effectue qu’une copie superficielle du champ _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

Cette méthode a résolu le problème pour moi:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

Utilisez-le comme ceci: MyObj a = DeepCopy(b);

J'aime les convertisseurs comme celui-ci:

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

Si vous avez plus de choses à copier, ajoutez-les

Générateur de code

Nous avons vu beaucoup d’idées de la sérialisation à la réflexion en passant par l’implémentation manuelle et je souhaite proposer une approche totalement différente en utilisant le Générateur de code CGbR . La méthode de génération de clone est efficace en termes de mémoire et de CPU et est donc 300 fois plus rapide que le DataContractSerializer standard.

Tout ce dont vous avez besoin est une définition de classe partielle avec ICloneable et le générateur fait le reste:

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

Remarque: la dernière version propose davantage de contrôles nuls, mais je les ai laissés de côté pour une meilleure compréhension.

Voici une solution rapide et facile qui a fonctionné pour moi sans relayer la sérialisation / désérialisation.

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

MODIFIER : nécessite

    using System.Linq;
    using System.Reflection;

C'est comme ça que je l'ai utilisé

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

Suivez ces étapes:

  • Définissez une ISelf<T> avec une propriété Self en lecture seule qui renvoie T et ICloneable<out T>, qui dérive de T Clone() et inclut une méthode CloneBase.
  • Définissez ensuite un type protected virtual generic VirtualClone qui implémente un MemberwiseClone transtypage VirtualClone vers le type transmis.
  • Chaque type dérivé doit implémenter sealed en appelant la méthode de base clone, puis en effectuant tout ce qui doit être fait pour cloner correctement les aspects du type dérivé que la méthode parent VirtualClone n'a pas encore gérés.

Pour une polyvalence maximale de l'héritage, les classes exposant les fonctionnalités de clonage public doivent être ICloneable<theNonCloneableType>, mais dériver d'une classe de base identique sinon à l'exception du manque de clonage. Plutôt que de passer des variables du type clonable explicite, prenez un paramètre de type Foo. Cela permettra à une routine qui attend un dérivé clonable de DerivedFoo de fonctionner avec un dérivé clonable de <=>, mais permet également la création de dérivés non clonables de <=>.

Je pense que vous pouvez essayer ceci.

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

J'ai créé une version de la réponse acceptée qui fonctionne avec '[Serializable]' et '[DataContract]'. Je l'ai écrit il y a longtemps, mais si je me souviens bien, DataContract avait besoin d'un autre sérialiseur.

Nécessite System, System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, 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;
    }

} 

Pour cloner votre objet de classe, vous pouvez utiliser la méthode Object.MemberwiseClone,

ajoutez simplement cette fonction à votre classe:

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

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

Pour effectuer une copie indépendante profonde, appelez simplement la méthode DeepCopy:

yourClass newLine = oldLine.DeepCopy();

espérons que cela aide.

Ok, il y a des exemples évidents de réflexion dans ce billet, MAIS la réflexion est généralement lente, jusqu'à ce que vous commenciez à la mettre en cache correctement.

si vous le cachez correctement, il va profondément cloner l’objet 1000000 par 4,6 s (mesuré par Watcher).

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

que vous prenez les propriétés mises en cache ou ajoutez nouveau au dictionnaire et les utilisez simplement

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

vérification complète du code dans mon message dans une autre réponse

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

Si votre arborescence d'objets est sérialisable, vous pouvez également utiliser quelque chose comme ceci

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

soyez informé que cette solution est assez simple mais qu'elle n'est pas aussi performante que d'autres solutions.

Et assurez-vous que si la classe grandit, il ne restera que les champs clonés, qui seront également sérialisés.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top