Objets de clonage profond


2229

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é, donc quand cela a été nécessaire, j'ai eu recours à la création d'un nouvel objet puis à la copie de chaque propriété individuellement, mais cela me laisse toujours le sentiment qu'il existe une manière meilleure ou plus élégante de gérer la situation.

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


81
Peut être utile: "Pourquoi copier un objet est-il une chose terrible à faire?" agiledeveloper.com/articles/cloning072002.htm
Pedro77


18
Vous devriez jeter un œil à AutoMapper
Daniel Little

3
Votre solution est beaucoup plus complexe, je me suis perdu en la lisant ... hehehe. J'utilise une interface DeepClone. interface publique IDeepCloneable <T> {T DeepClone (); }
Pedro77

1
@ Pedro77 - Bien que, ce qui est intéressant, cet article finit par dire de créer une cloneméthode sur la classe, puis de la faire appeler un constructeur interne et privé qui est passé this. Donc, la copie est turbulente [sic], mais la copie avec soin (et l'article mérite certainement d'être lu) ne l'est pas. ; ^)
ruffin

Réponses:


1717

Bien que la pratique standard consiste à implémenter l' ICloneableinterface (décrite ici , donc je ne régurgite pas), voici un joli copieur d'objets clone profond que j'ai trouvé sur The Code Project un certain temps et que incorporé dans nos trucs.

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

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", 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 puis le désérialise 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 les nouvelles 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) J'ai pensé revoir cela, pour mentionner que j'ai récemment commencé à utiliser (Newtonsoft) Json pour ce faire, il devrait être plus léger et éviter la surcharge des balises [Serializable]. ( NB @atconway a souligné dans les commentaires que les membres privés ne sont pas clonés en utilisant la méthode JSON)

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

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

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

24
stackoverflow.com/questions/78536/cloning-objects-in-c/… a un lien vers le code ci-dessus [et fait référence à deux autres implémentations de ce type, dont l'une est plus appropriée dans mon contexte]
Ruben Bartelink

102
La sérialisation / désérialisation implique des frais généraux importants qui ne sont pas nécessaires. Voir l'interface ICloneable et les méthodes de clonage .MemberWise () en C #.
3Dave

18
@David, d'accord, mais si les objets sont légers et que les performances atteintes lors de leur utilisation ne sont pas trop élevées pour vos besoins, c'est un conseil utile. Je ne l'ai pas utilisé intensivement avec de grandes quantités de données dans une boucle, je l'admets, mais je n'ai jamais vu un seul problème de performance.
johnc

16
@Amir: en fait, non: typeof(T).IsSerializableest également vrai si le type a été marqué avec l' [Serializable]attribut. Il n'a pas besoin d'implémenter l' ISerializableinterface.
Daniel Gehriger

11
Je pensais juste mentionner que bien que cette méthode soit utile, et que je l'ai utilisée moi-même de nombreuses fois, elle n'est pas du tout compatible avec Medium Trust - alors faites attention si vous écrivez du code qui a besoin de compatibilité. BinaryFormatter accède aux champs privés et ne peut donc pas fonctionner dans le jeu d'autorisations par défaut pour les environnements de confiance partielle. Vous pouvez essayer un autre sérialiseur, mais assurez-vous que votre appelant sait que le clone n'est peut-être pas parfait si l'objet entrant s'appuie sur des champs privés.
Alex Norcliffe

298

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

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

Vous pouvez également 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);
    }
}

13
le solutiojn est encore plus rapide que la solution BinaryFormatter, Comparaison des performances de sérialisation .NET
esskar

3
Merci pour cela. J'ai pu faire essentiellement la même chose avec le sérialiseur BSON fourni avec le pilote MongoDB pour C #.
Mark Ewer

3
C'est la meilleure façon pour moi, cependant, j'utilise Newtonsoft.Json.JsonConvertmais c'est la même chose
Pierre

1
Pour que cela fonctionne, l'objet à cloner doit être sérialisable comme déjà mentionné - cela signifie également par exemple qu'il peut ne pas avoir de dépendances circulaires
radomeit

2
Je pense que c'est la meilleure solution car l'implémentation peut être appliquée sur la plupart des langages de programmation.
mr5

178

La raison de ne pas utiliser ICloneable n'est pas parce qu'il n'a pas d'interface générique. La raison de ne pas l'utiliser est parce que c'est vague . Il n'est pas clair si vous obtenez une copie peu profonde ou profonde; c'est à l'implémenteur.

Oui, MemberwiseClonefait une copie superficielle, mais l'opposé de MemberwiseClonene l'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 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, c'est simplement de créer une Copyméthode qui fait exactement ce que je veux.


Je ne comprends pas pourquoi ICloneable est considéré comme vague. Étant donné un type comme Dictionary (Of T, U), je m'attendrais à ce qu'ICloneable.Clone fasse le niveau de copie profonde et superficielle nécessaire pour faire du nouveau dictionnaire un dictionnaire indépendant qui contient les mêmes T et U (contenu de la structure, et / ou références d'objet) comme l'original. Où est l'ambiguïté? Pour être sûr, un ICloneable générique (Of T), qui a hérité ISelf (Of T), qui comprenait une méthode "Self", serait beaucoup mieux, mais je ne vois pas d'ambiguïté sur le clonage profond vs superficiel.
supercat

31
Votre exemple illustre le problème. Supposons que vous ayez un dictionnaire <chaîne, client>. Le dictionnaire cloné doit-il avoir les mêmes objets client que l'original ou des copies de ces objets client? Il existe des cas d'utilisation raisonnables pour l'un ou l'autre. Mais ICloneable ne précise pas lequel vous obtiendrez. C'est pourquoi ce n'est pas utile.
Ryan Lundy

@ Kyralessa L'article Microsoft MSDN indique en fait ce problème même de ne pas savoir si vous demandez une copie profonde ou peu profonde.
écraser

La réponse du stackoverflow.com/questions/129389/… en double décrit l'extension Copy, basée sur le MembershipClone récursif
Michael Freidgeim

123

Après beaucoup de lecture sur de nombreuses options liées ici et sur les solutions possibles à ce problème, je pense que toutes les options sont assez 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 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!

Avant tout, ce sont toutes nos options:

L' article Fast Deep Copy par Expression Trees propose également une comparaison des performances du clonage par sérialisation, réflexion et expression.

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

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

Tous ses articles tournent autour d'un exemple qui tente de s'appliquer à la plupart des cas, en utilisant 3 objets: Personne , Cerveau et Ville . Nous voulons cloner une personne, qui aura son propre cerveau mais la même ville. Vous pouvez soit visualiser tous les problèmes que l'une des autres méthodes ci-dessus peut apporter ou lire l'article.

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

La copie d'un objet en spécifiant Newsuivi du nom de classe conduit souvent à un code qui n'est pas extensible. L'utilisation de clone, l'application du modèle de prototype, est un meilleur moyen d'y parvenir. Cependant, l'utilisation de clone tel qu'il est fourni en C # (et Java) peut é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 de clonage. Cela nous donne la possibilité de déléguer la tâche de création d'un objet à une instance d'une classe elle-même, offrant ainsi une extensibilité et également, la création en toute sécurité des objets à l'aide du constructeur de copie protégée.

Espérons que cette mise en œuvre 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);
    }
    
}

Envisagez maintenant d'avoir une classe dérivée 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 comptons le nombre d'objets, le clone tel qu'implémenté ici gardera un compte correct du nombre d'objets.


6
MS recommande de ne pas utiliser ICloneablepour les membres publics. "Parce que les appelants de Clone ne peuvent pas dépendre de la méthode effectuant une opération de clonage prévisible, nous recommandons qu'ICloneable ne soit pas implémenté dans les API publiques." msdn.microsoft.com/en-us/library/… Cependant, sur la base de l'explication donnée par Venkat Subramaniam dans votre article lié, je pense qu'il est logique d'utiliser dans cette situation tant que les créateurs des objets ICloneable ont une profonde comprendre quelles propriétés doivent être profondes par rapport aux copies superficielles (c.-à-d. copie profonde Brain, copie superficielle City)
BateTech

Tout d'abord, je suis loin d'être un expert dans ce sujet (API publiques). Je pense que pour une fois cette remarque de MS a beaucoup de sens. Et je ne pense pas qu'il soit sûr de supposer que les utilisateurs de cette API auront une compréhension aussi approfondie. Donc, cela n'a de sens de l'implémenter sur une API publique que si cela n'a vraiment pas d'importance pour celui qui va l'utiliser. Je suppose qu'avoir une sorte d'UML faisant explicitement la distinction sur chaque propriété pourrait aider. Mais j'aimerais entendre quelqu'un avec plus d'expérience. : P
cregox

Vous pouvez utiliser le générateur de clones CGbR et obtenir un résultat similaire sans écrire manuellement le code.
Toxantron

L'implémentation d'un langage intermédiaire est utile
Michael Freidgeim

Il n'y a pas de finale en C #
Konrad

84

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


5
.Net n'a pas de constructeurs de copie.
Pop Catalin

48
Bien sûr: nouveau MyObject (objToCloneFrom) Déclarez simplement un ctor qui prend l'objet à cloner comme paramètre.
Nick

30
Ce n'est pas la même chose. Vous devez l'ajouter manuellement à chaque classe, et vous ne savez même pas si vous garantissez une copie complète.
Dave Van den Eynde

15
+1 pour copie ctor. Vous devez également écrire manuellement une fonction clone () pour chaque type d'objet, et bonne chance lorsque la hiérarchie de votre classe atteint quelques niveaux.
Andrew Grant

3
Avec les constructeurs de copie, vous perdez la hiérarchie. agiledeveloper.com/articles/cloning072002.htm
Will

42

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

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}

15
Malheureusement, cela est imparfait. Cela équivaut à appeler objectOne.MyProperty = objectTwo.MyProperty (c'est-à-dire qu'il ne fera que copier la référence). Il ne clonera pas les valeurs des propriétés.
Alex Norcliffe

1
à Alex Norcliffe: auteur d'une question posée sur "copier chaque propriété" plutôt que sur le clonage. dans la plupart des cas, la duplication exacte des propriétés n'est pas nécessaire.
Konstantin Salavatov

1
je pense à utiliser cette méthode mais avec récursivité. donc si la valeur d'une propriété est une référence, créez un nouvel objet et appelez à nouveau CopyTo. je vois juste un problème, que toutes les classes utilisées doivent avoir un constructeur sans paramètres. Quelqu'un a déjà essayé ça? Je me demande également si cela fonctionnera réellement avec des propriétés contenant des classes .net comme DataRow et DataTable?
Koryu

33

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

Comment l'utiliser?

Au lieu d'écrire vos propres méthodes Cloneou Copyavec un ton d'affectations entre les champs et les propriétés, faites-le faire par vous-même, en utilisant l'arbre d'expression. GetClone<T>()La méthode marquée comme méthode d'extension vous permet de l'appeler simplement sur votre instance:

var newInstance = source.GetClone();

Vous pouvez choisir ce qui doit être copié de sourceà l' newInstanceaide de CloningFlagsenum:

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

Que peut-on cloner?

  • Primitifs (int, uint, octet, double, char, etc.), types immuables connus (DateTime, TimeSpan, String) et délégués (y compris Action, Func, etc.)
  • Nullable
  • Tableaux T []
  • Classes et structures personnalisées, y compris les classes et structures génériques.

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

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

Quelle est sa 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 d' GetClone<T>être utilisées pour la première fois pour un type donnéT .

C'est également plus rapide qu'une 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 dans la documentation .

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

.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
    System.Collections.Generic.List`1[System.Int32] $source,
    CloneExtensions.CloningFlags $flags,
    System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
    .Block(System.Collections.Generic.List`1[System.Int32] $target) {
        .If ($source == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        .If (
            .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
        ) {
            $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
            ).Invoke((System.Object)$source)
        } .Else {
            $target = .New System.Collections.Generic.List`1[System.Int32]()
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
        ) {
            .Default(System.Void)
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
        ) {
            .Block() {
                $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
                    $source.Capacity,
                    $flags,
                    $initializers)
            }
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
        ) {
            .Block(
                System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
                System.Collections.Generic.ICollection`1[System.Int32] $var2) {
                $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
                $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
                .Loop  {
                    .If (.Call $var1.MoveNext() != False) {
                        .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
                                $var1.Current,
                                $flags,


                         $initializers))
                } .Else {
                    .Break #Label2 { }
                }
            }
            .LabelTarget #Label2:
        }
    } .Else {
        .Default(System.Void)
    };
    .Label
        $target
    .LabelTarget #Label1:
}

}

ce qui a la même signification que de suivre le code c #:

(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 ça que vous écririez votre propre Cloneméthode List<int>?


2
Quelles sont les chances que cela arrive sur NuGet? Cela semble être la meilleure solution. Comment se compare-t-il à NClone ?
écraser

Je pense que cette réponse devrait être votée plusieurs fois. L'implémentation manuelle d'ICloneable est fastidieuse et sujette aux erreurs, l'utilisation de la réflexion ou de la sérialisation est lente si les performances sont importantes et que vous devez copier des milliers d'objets pendant une courte période.
nightcoder

Pas du tout, vous vous trompez sur la réflexion, vous devez simplement mettre cela en cache correctement. Vérifiez ma réponse ci-dessous stackoverflow.com/a/34368738/4711853
Roma Borodov

31

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

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}

31

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

MyObject oldObj; // The existing object to clone

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

En utilisant cette méthode, vous n'avez pas à implémenter ISerializableou ICloneablesur vos objets. Ceci est courant avec le modèle MVC / MVVM, donc des outils simples comme celui-ci ont été créés.

voir l'exemple de clonage profond ValueInjecter sur GitHub .


26

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. By Expression Trees - 20x plus rapide

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


le clonage de code à l'aide d'arbres d'expression que vous avez publié codeproject.com/Articles/1111658/… , échoue avec les versions plus récentes du framework .Net avec une exception de sécurité, l' opération pourrait déstabiliser le runtime , il s'agit essentiellement d'une exception due à une arborescence d'expressions malformée, qui est utilisé pour générer le Func au moment de l'exécution, veuillez vérifier si vous avez une solution.En fait, j'ai vu un problème uniquement avec des objets complexes avec une hiérarchie profonde, un simple peut être facilement copié
Mrinal Kamboj

1
La mise en œuvre d'ExpressionTree semble très bonne. Il fonctionne même avec des références circulaires et des membres privés. Aucun attribut requis. Meilleure réponse que j'ai trouvée.
N73k

La meilleure réponse, a très bien fonctionné, vous m'avez sauvé la journée
Adel Mourad

23

La réponse courte est que vous héritez de l'interface ICloneable puis implémentez la fonction .clone. Le clone doit effectuer une copie par membre et effectuer une copie complète sur tout membre qui en a besoin, puis renvoyer l'objet résultant. Il s'agit d'une opération récursive (elle requiert que tous les membres de la classe que vous souhaitez cloner soient soit des types de valeur, soit implémentent ICloneable et que leurs membres soient soit des types de valeur, soit implémentent ICloneable, etc.).

Pour une explication plus détaillée sur le clonage à l'aide d'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 fait considéré par certains comme une "erreur" dans le .NET Framework. La méthode de sérialisation dépend de la sérialisation de vos objets, ce qu'ils peuvent ne pas être et que vous n'avez aucun contrôle sur. Il y a encore beaucoup de débats dans la communauté sur la meilleure pratique. En réalité, aucune des solutions ne correspond à la meilleure pratique pour toutes les situations comme ICloneable a été interprétée à l'origine.

Consultez cet article du coin du développeur pour quelques autres options (crédit à Ian).


1
ICloneable n'a pas d'interface générique, il n'est donc pas recommandé d'utiliser cette interface.
Karg

Votre solution fonctionne jusqu'à ce qu'elle ait besoin de gérer des références circulaires, puis les choses commencent à se compliquer, il est préférable d'essayer d'implémenter un clonage profond en utilisant une sérialisation profonde.
Pop Catalin

Malheureusement, tous les objets ne sont pas non plus sérialisables, vous ne pouvez donc pas toujours utiliser cette méthode non plus. Le lien d'Ian est la réponse la plus complète à ce jour.
Zach Burlingame

19
  1. Fondamentalement, vous devez implémenter une 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 rapport avec la solution que vous choisissez) que tous les enfants sont également clonables.
  3. Parfois, vous devez être conscient d'une restriction 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 créer de clones de cet objet, ou si c'est possible, vous devez vous en soucier sur la session de fixation de ces objets.

À votre santé.


4
ICloneable n'a pas d'interface générique, il n'est donc pas recommandé d'utiliser cette interface.
Karg

Les réponses simples et concises sont les meilleures.
DavidGuaita

17

EDIT: le projet est interrompu

Si vous voulez un vrai clonage vers des types inconnus, vous pouvez jeter un œil à 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 graphique d'objet.

Cela signifie: 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 à cloner.


Celui-ci semble être assez utile
LuckyLikey

Il est plus facile de commencer à travailler à partir d'un instantané de code que pour un système global, en particulier un fermé. Il est tout à fait compréhensible qu'aucune bibliothèque ne puisse résoudre tous les problèmes d'un seul coup. Quelques relaxations devraient être faites.
TarmoPikaro

1
J'ai essayé votre solution et elle semble bien fonctionner, merci! Je pense que cette réponse devrait être votée plusieurs fois. L'implémentation manuelle d'ICloneable est fastidieuse et sujette aux erreurs, l'utilisation de la réflexion ou de la sérialisation est lente si les performances sont importantes et que vous devez copier des milliers d'objets pendant une courte période.
nightcoder

Je l'ai essayé et cela n'a pas fonctionné du tout pour moi. Lève une exception MemberAccess.
Michael Brown

Il ne fonctionne pas avec les nouvelles versions de .NET et est interrompu
Michael Sander

14

Gardez les choses simples et utilisez AutoMapper comme d'autres l'ont mentionné, c'est une simple petite bibliothèque pour mapper un objet à un autre ... Pour copier un objet à un autre avec le même type, tout ce dont vous avez besoin est 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;
}

La méthode d'extension peut être utilisée comme suit:

MyType copy = source.Copy();

Soyez prudent avec celui-ci, il fonctionne vraiment mal. J'ai fini par passer à la réponse johnc qui est aussi courte que celle-ci et qui fonctionne beaucoup mieux.
Agorilla

1
Cela ne fait qu'une copie superficielle.
N73k

11

Je suis venu avec cela pour surmonter un .NET lacune devant copier manuellement 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 trouver un oneliner qui fait cela, mais ce n'est pas possible, car le rendement ne fonctionne pas à l'intérieur des blocs de méthodes anonymes.

Mieux encore, utilisez le cloneur 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();
        }
    }
}

10

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

  • Choisissez cette réponse si vous voulez que la vitesse la plus rapide dont .NET soit capable.
  • 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 des performances qui doit être corrigé, et vous pouvez le prouver avec un profileur .

10 fois plus rapide que les autres méthodes

La méthode suivante pour effectuer un clone profond est:

  • 10 fois plus rapide que tout ce qui implique une sérialisation / désérialisation;
  • Assez proche de la vitesse maximale théorique dont .NET est capable.

Et la méthode ...

Pour une vitesse ultime, vous pouvez utiliser Nested MemberwiseClone pour effectuer une copie complète . C'est presque la même vitesse que la copie d'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 Nested MemberwiseClone pour une copie complète , vous devez implémenter manuellement un ShallowCopy pour chaque niveau imbriqué de la classe et un DeepCopy qui appelle toutes lesdites méthodes ShallowCopy pour créer un clone complet. C'est simple: seulement 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 secondes pour Nested MemberwiseClone sur les structures imbriquées
  • 4,77 secondes pour Nested MemberwiseClone sur les classes imbriquées
  • 39,93 secondes pour la sérialisation / désérialisation

L'utilisation de Nested MemberwiseClone sur une classe presque aussi vite que la copie d'une structure, et la copie d'une structure est sacrément proche de la vitesse maximale théorique dont .NET est capable.

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 faire une copie complète à l'aide de MemberwiseCopy, voici le projet de démonstration qui a été 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 un ShallowCopy pour chaque niveau imbriqué de la classe et un DeepCopy qui appelle toutes lesdites méthodes ShallowCopy pour créer un clone complet. C'est simple: seulement quelques lignes au total, voir le code de démonstration ci-dessus.

Types de valeurs vs types de références

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

  • Si vous avez un " struct ", c'est un type de valeur , vous pouvez donc simplement le copier, et le contenu sera cloné (mais il ne fera qu'un clone superficiel à moins que vous n'utilisiez les techniques de cet article).
  • Si vous avez une " classe ", c'est un type de référence , donc si vous la copiez, tout ce que vous faites est de copier le pointeur dessus. Pour créer un véritable clone, vous devez être plus créatif et utiliser des 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 les différences entre les types de valeurs et les types de références .

Sommes de contrôle pour faciliter le débogage

  • Un clonage incorrect d'objets peut entraîner des bogues très difficiles à identifier. Dans le code de production, j'ai tendance à implémenter une somme de contrôle pour vérifier que l'objet a été cloné correctement 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 consiste à alimenter des clones d'une classe ou d'une structure imbriquée dans une file d'attente, pour implémenter le modèle producteur / consommateur.

  • Nous pouvons avoir un (ou plusieurs) threads modifiant une classe dont ils sont propriétaires, puis en 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 plusieurs threads (les consommateurs).

Et cette méthode est également incroyablement rapide: si nous utilisons des structures imbriquées, elle est 35 fois plus rapide que la sérialisation / désérialisation de 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 ci-dessus. Je devrais peut-être voir comment ils se comparent à un profileur.


Si vous copiez une structure, vous obtenez une copie superficielle, vous pourriez toujours avoir besoin d'une implémentation spécifique pour une copie complète.
Lasse V. Karlsen

@Lasse V. Karlsen. Oui, vous avez absolument raison, j'ai mis à jour la réponse pour clarifier les choses. Cette méthode peut être utilisée pour créer des copies complètes de structures et de classes. Vous pouvez exécuter l'exemple de code de démonstration inclus pour montrer comment cela se fait, il a un exemple de clonage en profondeur d'une structure imbriquée et un autre exemple de clonage en profondeur d'une classe imbriquée.
Contango

9

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

Pour une copie complète, il n'y a aucun moyen de savoir comment le faire automatiquement.


ICloneable n'a pas d'interface générique, il n'est donc pas recommandé d'utiliser cette interface.
Karg

8

Je l'ai également vu implémenté par la réflexion. Fondamentalement, il existait une méthode permettant de parcourir les membres d'un objet et de les copier de manière appropriée vers le nouvel objet. Quand il a atteint des types de référence ou des collections, je pense qu'il a fait un appel récursif sur lui-même. La réflexion coûte cher, mais elle a plutôt bien fonctionné.


8

Voici une implémentation de copie approfondie:

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

2
Cela ressemble à un clone membre car il ne connaît pas les propriétés du type de référence
sll

1
Si vous voulez des performances incroyablement rapides, n'allez pas pour cette implémentation: elle utilise la réflexion, donc ce ne sera pas si rapide. Inversement, "l'optimisation prématurée est le mal de tous", alors ignorez le côté performances jusqu'à ce que vous ayez exécuté un profileur.
Contango

1
CreateInstanceOfType n'est pas défini?
MonsterMMORPG

Il échoue sur interger: "La méthode non statique nécessite une cible."
Mr.B

8

Comme je n'ai pas pu trouver un cloneur qui réponde à toutes mes exigences dans différents projets, j'ai créé un cloneur 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. Son atteint en ajoutant des annotations au code qui doit être cloné ou vous laissez simplement le code tel qu'il est d'avoir le comportement par défaut. Il utilise la réflexion, saisit des caches et est basé sur un fléchissement 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

Également disponible sous forme de package nuget: https://www.nuget.org/packages/Clone.Behave/1.0.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

7

Générateur de code

Nous avons vu beaucoup d'idées allant de la sérialisation à l'implémentation manuelle en passant par la réflexion et je veux 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 mémoire et en processeur et donc 300 fois plus rapide que le DataContractSerializer standard.

Tout ce dont vous avez besoin est une définition de classe partielle avec ICloneableet 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 a une vérification plus nulle, mais je les ai laissés pour une meilleure compréhension.


6

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

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


6

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

Voilà comment je l'ai utilisé

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

5

Suivez ces étapes:

  • Définissez un ISelf<T>avec une Selfpropriété en lecture seule qui renvoie T, et ICloneable<out T>, qui dérive ISelf<T>et inclut une méthode T Clone().
  • Définissez ensuite un CloneBasetype qui implémente une protected virtual generic VirtualCloneconversion MemberwiseClonevers le type transmis.
  • Chaque type dérivé doit implémenter VirtualCloneen appelant la méthode de clone de base, puis en faisant tout ce qui doit être fait pour cloner correctement les aspects du type dérivé que la méthode VirtualClone parent n'a pas encore pris en charge.

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


5

Je pense que vous pouvez essayer ça.

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

4

J'ai créé une version de la réponse acceptée qui fonctionne à la fois avec «[Serializable]» et «[DataContract]». Cela fait un moment que je ne l'ai pas écrit, mais si je me souviens bien, [DataContract] avait besoin d'un sérialiseur différent.

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

} 

4

Ok, il y a un exemple évident de 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, alors il clone en profondeur l'objet 1000000 par 4,6s (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 nouveaux au dictionnaire et utilisez-les 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


2
L'appel prop.GetValue(...)est toujours une réflexion et ne peut pas être mis en cache. Dans un arbre d'expression, il est compilé, donc plus vite
Tseng

4

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 résolu tous les besoins ici. Je n'ai pas réussi à faire fonctionner la sérialisation dans un scénario compliqué avec une structure complexe, et ce IClonablen'est pas idéal - en fait, cela ne devrait même pas être nécessaire.

Attributs ignorer standard sont pris en charge à l' aide [IgnoreDataMember], [NonSerialized]. Prend en charge les collections complexes, les propriétés sans setters, les champs en lecture seule, etc.

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


4

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

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

La sérialisation est limitative (nécessite des attributs, des constructeurs spécifiques, etc.) et est très lente

BinaryFormatterrequiert l' Serializableattribut, JsonConverternécessite un constructeur ou des attributs sans paramètre, ni ne gère très bien les champs ou les interfaces en lecture seule et les deux sont 10-30x plus lents que nécessaire.

Arbres d'expression

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

Ayant moi-même rencontré le problème et ne voyant aucune solution satisfaisante, j'ai décidé de créer un package qui fait exactement cela et qui fonctionne avec tous les types et qui est presque aussi rapide qu'un code écrit personnalisé .

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

Usage

Vous pouvez l'installer depuis NuGet. Soit obtenez le ObjectClonerpackage et utilisez-le comme:

var clone = ObjectCloner.DeepClone(original);

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

var clone = original.DeepClone();

Performance

Un simple test de clonage d'une hiérarchie de classes a montré des performances ~ 3 fois plus rapides qu'avec Reflection, ~ 12 fois plus rapides que la sérialisation Newtonsoft.Json et ~ 36 fois plus rapides que celles fortement suggérées BinaryFormatter.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.