web-dev-qa-db-fra.com

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 apportez 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é, 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 le sentiment qu’il existe une méthode de traitement meilleure ou plus élégante. la situation.

Comment puis-je cloner ou copier en profondeur un objet de manière à ce que l'objet cloné puisse être modifié sans qu'aucune modification ne soit reflétée dans l'objet d'origine?

2088
NakedBrunch

Alors que la pratique habituelle est d’implémenter l’interface ICloneable (décrite ici , je ne régurgiterai donc pas), voici un copieur d’objets de clonage profond de Nice que j’ai trouvé sur The Code Project il y a quelque temps et l'a incorporé dans nos fichiers.

Comme mentionné ailleurs, 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.", nameof(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 et le désérialise ensuite en un nouvel objet. L'avantage est que vous n'avez pas à vous soucier de tout cloner 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 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();.

EDIT (10 janvier 2015) Je pensais y revenir, pour mentionner que j'ai récemment commencé à utiliser (Newtonsoft) Json pour le faire - devrait être plus léger et évite la surcharge des balises [Serializable]. (NB @atconway a indiqué dans les 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);
}
1628
johnc

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

En outre, 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);
    }
}
257
craastad

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

Oui, MemberwiseClone crée une copie superficielle, mais l'inverse 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 clarifieront pas, 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 Copy qui fait exactement ce que je veux.

169
Ryan Lundy

Après avoir beaucoup lu sur les nombreuses options liées ici et sur les solutions possibles à ce problème, je crois que toutes les options sont résumées plutôt bien sur le lien de Ian P les autres options sont des variantes de celles-ci) et la meilleure solution est fournie par le lien de Pedro77 dans 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!

Tout d’abord, c’est toutes nos options:

Le article Copie rapide en profondeur par les arbres d'expression propose également une comparaison des performances du clonage par arbres 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 .

Tout son article tourne autour d'un exemple qui tente d'être applicable dans la plupart des cas, en utilisant 3 objets: Personne, Cervea 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 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 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 d'une 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, envisagez de faire en sorte que la 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.

106
cregox

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

81
Nick

Méthode d'extension simple pour copier toutes les propriétés publiques. Fonctionne pour tous les objets et ne le fait pas requiert 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 ) } );
        }
    };
}
40

Eh bien, j’avais des problèmes pour utiliser ICloneable dans Silverlight, mais j’ai aimé l’idée de la sérialisation, je peux sérialiser XML, alors j’ai fait ceci:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    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);
    }
}
32
Michael White

Je viens de créer bibliothèque CloneExtensions. 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, à l’aide de Expression Tree. La méthode GetClone<T>() 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.), types immuables connus (DateTime, TimeSpan, String) et 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 de public, pas de champs en lecture seule
  • Valeurs des propriétés publiques avec des accesseurs get et set
  • Eléments de collection pour les types implémentant ICollection

A quelle vitesse?

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

Elle est également plus rapide que la solution basée sur la sérialisation lorsque vous clonez plus de deux 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:
}

}

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 Clone pour List<int>?

29
MarcinJuraszek

Si vous utilisez déjà une application tierce telle que ValueInjecter ou Automapper , vous pouvez procéder de la manière suivante:

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. Ceci est commun avec le modèle MVC/MVVM, des outils simples comme celui-ci ont été créés.

voir la solution de clonage en profondeur valueinjecter sur CodePlex .

27
Michael Cox

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

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

puis l'utiliser n'importe où dans la solution en

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 - 5 fois plus rapide
  3. Par arbres d’expression - 20 fois plus rapide

Toutes les méthodes liées fonctionnent bien et ont été testées en profondeur.

21
frakon

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 (tous les membres de la classe que vous voulez cloner doivent être des types valeur ou implémenter ICloneable et leurs membres être des types valeur ou implémenter 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 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 la "meilleure" pratique. 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 this article du coin du développeur pour quelques options supplémentaires (crédit de Ian).

21
Zach Burlingame
  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 intégrale de tous les membres, vous devez vous assurer que tous les enfants sont clonables (sans indiquer la solution que vous choisissez).
  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 prendre soin de sur la session attachant de ces objets.

À votre santé.

17
dimarzionist

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

Il s’agit d’un clonage basé sur l’expression fonctionnant environ 10 fois plus rapidement que la sérialisation binaire et en maintenant l’intégrité complète du graphe d’objet.

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

Il n'y a pas besoin d'interfaces, d'attributs ou de toute autre modification des objets en cours de clonage.

16
Michael Sander

Gardez les choses simples et utilisez AutoMapper comme d'autres l'ont mentionné, c'est une simple petite bibliothèque pour mapper un objet sur un autre ... Pour copier un objet sur un autre avec le 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();
12
Stacked

Je suis venu avec cela pour surmonter un . NET défaut d'avoir manuellement copier en profondeur Deep List <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 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 cloner générique List <T>:

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

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

  • Choisissez cette réponse si vous voulez que 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, allez avec une autre réponse, sauf si vous avez un goulot d'étranglement qui doit être corrigé et que 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;
  • Joliment proche de la vitesse maximale théorique dont est capable NET.

Et la méthode ...

Pour une vitesse ultime, vous pouvez utiliser Nested MemberwiseClone pour effectuer une copie complète . Sa vitesse est presque identique à celle de la copie d'une structure de valeur et elle 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 Nested MemberwiseClone pour une copie en profondeur , vous avez pour implémenter manuellement une ShallowCopy pour chaque niveau imbriqué de la classe et une DeepCopy qui appelle toutes les méthodes dites ShallowCopy afin de 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 montrant 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 complète à l'aide de MemberwiseCopy, voici le projet de démonstration utilisé pour générer les temps ci-dessus:

// 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 depuis 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 complète , vous devez implémenter manuellement une ShallowCopy pour chaque niveau imbriqué de la classe et une DeepCopy qui appelle toutes les méthodes dites ShallowCopy afin de créer un clone complet. C'est simple: seulement 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 un " classe ":

  • Si vous avez un " struct ", c'est un type de valeur donc vous pouvez simplement le copier, et le contenu sera cloné (mais cela ne fera qu’un clone superficiel, sauf si vous utilisez les techniques décrites dans ce post).
  • Si vous avez une " classe ", c'est un type de référence , donc si vous le copiez, vous ne faites que copier le pointeur sur celui-ci. 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 les types de référence .

Checksums pour aider au débogage

  • Cloner des objets de manière incorrecte 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 Release.
  • Je trouve cette méthode assez utile: souvent, vous ne voulez que cloner des parties de l'objet, pas la totalité.

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

Un excellent cas d'utilisation de ce code est de nourrir 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 tirant des copies de ces classes et les traitant.

Cela fonctionne extrêmement bien dans la pratique et nous permet de découpler de nombreux threads (les producteurs) d'un ou de 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.

Mise à jour

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

9
Contango

Je l'ai vu également mis en œuvre par la réflexion. En gros, il existait une méthode qui parcourait les membres d'un objet et les copiait correctement 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é.

8
xr280xr

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 pour répondre aux exigences des cloneurs. Pour ce faire, ajoutez des annotations au code à cloner ou laissez le code tel quel pour avoir 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.

Par exemple: Le code suivant va deepClone Address, mais effectue uniquement 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
8
kalisohn

En règle générale, vous implémentez l'interface pouvant être marquée et le code vous-même. 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.

8
HappyDude

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

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

7
JerryGoyal

Je pense que tu peux essayer ça.

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

J'aime les copyconstructors comme ça:

    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

6
LuckyLikey

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 clonage utilise efficacement la mémoire et le processeur 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 dispose d'un plus grand nombre de contrôles nuls, mais je les ai laissés de côté pour une meilleure compréhension.

6
Toxantron

Voici une solution rapide et facile qui a fonctionné pour moi sans relayer sur 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;
    }
}

EDIT: 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();
}
6
Daniele D.

Suivez ces étapes:

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

Pour une polyvalence maximale en matière d'héritage, les classes présentant la fonctionnalité de clonage public doivent être sealed, mais dériver d'une classe de base qui est par ailleurs identique, sauf en l'absence de clonage. Plutôt que de passer des variables du type clonable explicite, prenons un paramètre de type ICloneable<theNonCloneableType>. Cela permettra à une routine qui attend un dérivé clonable de Foo de travailler avec un dérivé clonable de DerivedFoo, mais permet également la création de dérivés non clonables de Foo.

5
supercat

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 sérialiseur différent.

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

} 
4
Jeroen Ritmeijer

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

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

4
LuckyLikey

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

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

yourClass newLine = oldLine.DeepCopy();

j'espère que cela t'aides.

4
Chtiwi Malek

Ok, il y a quelques exemples évidents avec réflexion dans ce post, MAIS la réflexion est généralement lente, jusqu’à ce que vous commenciez à la mettre en cache correctement.

si vous le cachez correctement, vous obtiendrez un objet clone 1000000 profond de 4,6 s (mesuré par Watcher).

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

que vous prenez des propriétés en cache ou ajoutez de nouvelles au dictionnaire et les utilisez simplement

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

vérification du code complet dans mon post dans une autre réponse

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

4
Roma Borodov

Comme presque toutes les réponses à cette question ont été insatisfaisantes ou ne fonctionnent manifestement pas dans ma situation, j’ai écrit AnyClone , qui est entièrement mis en œuvre avec réflexion et a résolu tous les besoins ici. Je ne pouvais pas faire en sorte que la sérialisation fonctionne dans un scénario complexe avec une structure complexe, et IClonable est loin d'être idéal - en fait, cela ne devrait même pas être nécessaire.

Les attributs ignorés standard sont pris en charge avec [IgnoreDataMember], [NonSerialized]. Prend en charge les collections complexes, les propriétés sans régleurs, les champs en lecture seule, etc.

J'espère que cela aidera quelqu'un d'autre qui a rencontré les mêmes problèmes que moi.

3
Michael Brown

Lorsqu’on utilise Marc Gravells protobuf-net comme sérialiseur, la réponse acceptée nécessite quelques légères modifications, car l’objet à copier ne sera pas attribué à [Serializable] et, par conséquent, n’est pas sérialisable et la méthode Clone renvoie un exception.
Je l'ai modifié pour fonctionner avec protobuf-net:

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    IFormatter formatter = ProtoBuf.Serializer.CreateFormatter<T>();
    using (Stream stream = new MemoryStream())
    {
        formatter.Serialize(stream, source);
        stream.Seek(0, SeekOrigin.Begin);
        return (T)formatter.Deserialize(stream);
    }
}

Ceci vérifie la présence d'un attribut [ProtoContract] et utilise le propre formateur Protobufs pour sérialiser l'objet.

2
Basti M

Les approches génériques sont toutes techniquement valables, mais je souhaitais simplement ajouter une note personnelle, car nous avons rarement besoin d'une véritable copie en profondeur. endroits où les objets sont copiés puis modifiés explicitement, il est facile de se perdre.

Dans la plupart des situations réelles, vous souhaitez également avoir le plus grand contrôle possible sur le processus de copie, car vous êtes non seulement couplé au cadre d'accès aux données, mais également dans la pratique, les objets métier copiés doivent rarement être identiques à 100%. Pensez à un exemple de referenceId utilisé par l'ORM pour identifier les références d'objet. Une copie complète complète copiera également cet identifiant. De toute façon, vous devez modifier ces propriétés manuellement après la copie et si l’objet change, vous devez l’ajuster dans tous les emplacements qui utilisent la copie profonde générique.

En développant la réponse @cregox avec ICloneable, qu’est-ce qu’une copie en profondeur? C'est juste un objet nouvellement alloué sur le tas qui est identique à l'objet d'origine mais occupe un espace mémoire différent, en tant que tel plutôt que d'utiliser une fonctionnalité de clonage générique, pourquoi ne pas simplement créer un nouvel objet?

J'utilise personnellement l'idée de méthodes de fabrique statique sur mes objets de domaine.

Exemple:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

Si quelqu'un cherche comment structurer l'instanciation d'objet tout en conservant le contrôle total du processus de copie, c'est une solution avec laquelle j'ai personnellement eu beaucoup de succès. Les constructeurs protégés le font également, les autres développeurs sont obligés d'utiliser les méthodes d'usine qui donnent un point unique d'instanciation d'objet encapsulant la logique de construction à l'intérieur de l'objet. Vous pouvez également surcharger la méthode et avoir plusieurs logiques de clonage pour différents endroits si nécessaire.

2

Extension C # qui supporte aussi les types "not ISerializable".

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

Usage

       var obj2 = obj1.DeepClone()
2
Sameera R.

C'est incroyable les efforts que vous pouvez déployer avec l'interface IClonable, surtout si vous avez des hiérarchies de classes lourdes. Aussi, MemberwiseClone fonctionne-t-il étrangement - il ne clone pas exactement même les structures de type Liste normales.

Et bien sûr, le dilemme le plus intéressant pour la sérialisation est de sérialiser les références arrières - par exemple. hiérarchies de classes où vous avez des relations enfant-parent. Je doute que le sérialiseur binaire puisse vous aider dans ce cas. (Il finira avec des boucles récursives + débordement de pile).

J'ai en quelque sorte aimé la solution proposée ici: Comment faire une copie complète d'un objet dans .NET (C # en particulier)?

cependant - il ne soutenait pas les listes, a ajouté que ce soutien tenait également compte du changement de parentage. Pour les règles concernant uniquement le rôle parental que j'ai créées, ce champ ou cette propriété doit être nommé "parent"; DeepClone l'ignorera. Vous voudrez peut-être décider de vos propres règles pour les références arrière - pour les hiérarchies d’arbres, il peut s’agir de "gauche/droite", etc.

Voici l'extrait de code complet, y compris le code de test:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;


            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }


    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name="type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name="obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);

                for (int i = 0; i < array.Length; i++)
                    copied.SetValue(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child's parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}
2
TarmoPikaro

Encore une autre réponse JSON.NET. Cette version fonctionne avec des classes qui n'implémentent pas ISerializable.

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

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

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}
2
Matthew Watson

Un mappeur effectue une copie en profondeur. Pour chaque membre de votre objet, il crée un nouvel objet et assigne toutes ses valeurs. Cela fonctionne de manière récursive sur chaque membre interne non primitif.

Je vous suggère l’un des plus rapides, actuellement activement développé. Je suggère UltraMapper https://github.com/maurosampietro/UltraMapper

Nuget packages: https://www.nuget.org/packages/UltraMapper/

2
Mauro Sampietro

J'ai trouvé une nouvelle façon de le faire qui est Emit.

Nous pouvons utiliser Emit pour ajouter l'IL à l'application et l'exécuter. Mais je ne pense pas que ce soit un bon moyen car je veux perfectionner ce que j'écris ma réponse.

L’émission peut voir le document officiel et Guide

Vous devriez apprendre quelque chose pour lire le code. Je vais écrire le code qui peut copier la propriété en classe.

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

Le code peut être une copie profonde mais il peut copier la propriété. Si vous voulez le faire en copie profonde, vous pouvez le changer car il est trop difficile pour que je puisse le faire.

1
lindexi

que diriez-vous de simplement refondre à l'intérieur d'une méthode qui devrait invoquer essentiellement un constructeur de copie automatique

T t = new T();
T t2 = (T)t;  //eh something like that

        List<myclass> cloneum;
        public void SomeFuncB(ref List<myclass> _mylist)
        {
            cloneum = new List<myclass>();
            cloneum = (List < myclass >) _mylist;
            cloneum.Add(new myclass(3));
            _mylist = new List<myclass>();
        }

semble fonctionner pour moi

1
will_m

Le clonage en profondeur concerne la copie state. Pour .netétat signifie champs.

Disons que l'on a une hiérarchie:

static class RandomHelper
{
    private static readonly Random random = new Random();

    public static int Next(int maxValue) => random.Next(maxValue);
}

class A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}

class B : A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}

class C : B
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}

Le clonage peut être fait:

static class DeepCloneExtension
{
    // consider instance fields, both public and non-public
    private static readonly BindingFlags bindingFlags =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    public static T DeepClone<T>(this T obj) where T : new()
    {
        var type = obj.GetType();
        var result = (T)Activator.CreateInstance(type);

        do
            // copy all fields
            foreach (var field in type.GetFields(bindingFlags))
                field.SetValue(result, field.GetValue(obj));
        // for every level of hierarchy
        while ((type = type.BaseType) != typeof(object));

        return result;
    }
}

Demo1:

Console.WriteLine(new C());
Console.WriteLine(new C());

var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Résultat:

C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17

Image: C.random = 96 B.random = 18 A.random = 46

C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18

Clone: C.random = 96 B.random = 18 A.random = 46

C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79

Notez que tous les nouveaux objets ont des valeurs aléatoires pour le champ random, mais que clone correspond exactement à image.

Demo2:

class D
{
    public event EventHandler Event;
    public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}

// ...

var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");

image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");

image.RaiseEvent();
image.RaiseEvent();

var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");

clone.RaiseEvent();
image.RaiseEvent();

Résultat:

Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728

Notez que le champ de sauvegarde d'événement est également copié et que le client est également abonné à l'événement du clone.

1
Ted Mucuzany

Cela copiera toutes les propriétés lisibles et inscriptibles d'un objet sur un autre.

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

et voici comment vous l'utilisez:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

ou tout copier:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
1
Ylli Prifti

Avertissement: je suis l'auteur du package mentionné.

J'ai été surpris de voir comment les principales réponses à cette question de 2019 utilisent encore la sérialisation ou la réflexion.

La sérialisation est limitante (requiert des attributs, des constructeurs spécifiques, etc.) et est très lente

BinaryFormatter requiert l'attribut Serializable, JsonConverter nécessite un constructeur ou des attributs sans paramètre, ni les champs en lecture seule, ni les interfaces, et les deux sont 10-30x plus lents que nécessaire.

Arbres d'expression

Vous pouvez utiliser à la place les arbres d'expression ou Reflection.Emit pour générer le code de clonage une seule fois, puis utilisez ce code compilé au lieu d'une réflexion lente. ou la sérialisation.

Après avoir rencontré le problème moi-même et ne voyant aucune solution satisfaisante, j'ai décidé de créer un paquetage qui fait exactement cela et fonctionne avec tous les types et constitue un code presque aussi rapide que le code personnalisé .

Vous pouvez trouver le projet sur GitHub: https://github.com/marcelltoth/ObjectCloner

Usage

Vous pouvez l'installer à partir de NuGet. Soit le package ObjectCloner et utilisez-le comme suit:

var clone = ObjectCloner.DeepClone(original);

ou si cela ne vous dérange pas de polluer votre type d'objet avec les extensions, obtenez également ObjectCloner.Extensions et écrivez:

var clone = original.DeepClone();

Performance

Un simple test de clonage d'une hiérarchie de classes a montré des performances environ 3 fois plus rapides que l'utilisation de Reflection, environ 12 fois plus rapides que la sérialisation Newtonsoft.Json et environ 36 fois plus rapides que le très suggéré BinaryFormatter.

0
Marcell Toth

Utiliser System.Text.Json:

https://devblogs.Microsoft.com/dotnet/try-the-new-system-text-json-apis/

public static T DeepCopy<T>(this T source)
{
    return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}

La nouvelle API utilise Span<T>. Cela devrait être rapide, serait bien de faire quelques repères.

Remarque: Il n'est pas nécessaire d'utiliser ObjectCreationHandling.Replace comme dans Json.NET car il remplacera les valeurs de collection par défaut. Vous devriez oublier Json.NET maintenant car tout va être remplacé par la nouvelle API officielle.

Je ne suis pas sûr que cela fonctionne avec des champs privés.

0
Konrad

Paquet Nuget rapide, facile et efficace pour résoudre le clonage

Après avoir lu toutes les réponses, j'ai été surpris que personne ne mentionne cet excellent package:

https://github.com/force-net/DeepCloner

En élaborant un peu sur son readme, voici la raison pour laquelle nous l’avons choisi au travail:

Clause de non-responsabilité - Conditions requises:

  • .NET 4.0 ou supérieur ou .NET Standard 1.3 (.NET Core)
  • Nécessite un ensemble d'autorisations Full Trust ou Reflection (MemberAccess)
  • Il peut copie profonde ou peu profonde
  • Dans le clonage en profondeur, tout le graphe d'objet est maintenu.
  • Utilise la génération de code dans l'exécution, car le clonage résultant est extrêmement rapide
  • Objets copiés par la structure interne, pas de méthodes ni de ctors appelés
  • Vous n'avez pas besoin de marquer les classes d'une manière ou d'une autre (comme l'attribut Serializable ou d'implémenter des interfaces)
  • Aucune exigence de spécifier le type d'objet pour le clonage. L'objet peut être converti en interface ou en tant qu'objet abstrait (vous pouvez par exemple cloner un tableau d'ints sous la forme d'un tableau abstrait ou IEnumerable; même null peut être cloné sans erreur).
  • L'objet cloné n'a aucune capacité à déterminer qu'il est clone (sauf avec des méthodes très spécifiques)

L'utilisation est-ce facile:

  var deepClone = new { Id = 1, Name = "222" }.DeepClone();
  var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
0
alexlomba87