Le clonage d'objets profonds

je veux faire quelque chose comme:

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

, puis apporter des modifications au nouvel objet qui ne sont pas reflétées dans l'objet original.

Je n'ai pas souvent besoin de cette fonctionnalité, donc quand cela a été nécessaire, j'ai eu recours à créer un nouvel objet et ensuite copier chaque propriété individuellement, mais cela me laisse toujours avec le sentiment qu'il y a une meilleure ou plus élégante façon de gérer la situation.

Comment peut Je clone ou copie en profondeur un objet pour que l'objet cloné puisse être modifié sans qu'aucun changement ne se reflète dans l'objet original?

1883
demandé sur poke 2008-09-17 04:06:27

30 réponses

alors que la pratique standard est de mettre en œuvre l'interface ICloneable (décrit ici , donc je ne vais pas régurgiter), voici un copieur d'objet Deep clone nice j'ai trouvé sur le projet de Code Il ya un certain temps et l'a incorporé dans nos affaires.

comme mentionné ailleurs, il exige que vos objets soient sérialisables.

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

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

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

l'idée est qu'il sérialise votre objet et désérialise dans une nouvelle objet. L'avantage est que vous n'avez pas à vous soucier de tout cloner quand un objet devient trop complexe.

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

dans le cas où vous préférez utiliser la nouvelle méthodes d'extension de C# 3.0, changer 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é que je revisiterais ceci, pour mentionner que j'ai récemment commencé à utiliser (Newtonsoft) Json pour ce faire, il devrait être plus léger, et évite la surbrillance des étiquettes [sérialisables]. ( 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);
}
1495
répondu johnc 2017-05-23 12:18:36

je voulais un Cloneur pour des objets très simples, pour la plupart des primitifs et des listes. Si votre objet est sorti de la boîte JSON serialisable alors cette méthode fera l'affaire. Cela ne nécessite aucune modification ou implémentation d'interfaces sur la classe clonée, juste un serializer JSON comme JSON.NET.

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

aussi, 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);
    }
}
192
répondu craastad 2018-09-14 06:16:54

la raison de ne pas utiliser ICloneable est et non parce qu'il n'a pas d'interface générique. la raison pour ne pas l'utiliser est qu'il est vague . Il n'est pas clair si vous recevez une copie superficielle ou une copie profonde; c'est à l'exécutant de décider.

Oui, MemberwiseClone fait une copie superficielle, mais à l'opposé de MemberwiseClone n'est pas Clone ; il 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 des commentaires XML ne le fera pas clair, parce que vous obtiendrez l'interface des commentaires plutôt que sur l'objet de la méthode Clone.)

ce que je fais habituellement est tout simplement faire une méthode Copy qui fait exactement ce que je veux.

152
répondu Ryan Lundy 2015-10-07 18:37:12

après beaucoup de lecture sur beaucoup des options liées ici, et des solutions possibles pour cette question, je crois toutes les options sont résumées assez bien à Ian P 's link (toutes les autres options sont des variations de ceux-ci) et la meilleure solution est fournie par Pedro77 's link sur les commentaires question.

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

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

tout d'abord, ce sont toutes nos options:

l'article Fast Deep Copy by Expression Trees a aussi la comparaison de performance du clonage par sérialisation, réflexion et Expression Arbre.

pourquoi je choisis ICloneable (i.e. manuellement)

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

tous ses articles tournent autour d'un exemple qui tente d'être applicable dans la plupart des cas, en utilisant 3 objets: personne , cerveau et ville . Nous voulons clone d'une personne, qui disposera de son propre cerveau, mais la même ville. Vous pouvez soit visualiser tous les problèmes l'une des autres méthodes ci-dessus peut apporter ou lire l'article.

C'est ma version légèrement modifiée de sa conclusion:

copier un objet en spécifiant New suivi du nom de classe conduit souvent à un code qui n'est pas extensible. L'utilisation du clone, l'application du modèle de prototype, est une meilleure façon d'atteindre cet objectif. Cependant, en utilisant clone comme est fourni en C# (et Java) peut ê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, en fournissant ainsi l'extensibilité et aussi, en créant en toute sécurité les objets en utilisant le 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);
    }
    …
}

maintenant envisager d'avoir une classe dériver de la personne.

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

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

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

la sortie produite sera:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

Observer que, si nous tenons compte du nombre d'objets, le clone mis en œuvre ici gardera un bon de compter le nombre d'objets.

90
répondu cregox 2018-01-23 20:30:46

je préfère un copieur à un clone. L'intention est claire.

70
répondu Nick 2012-12-03 17:21:32

méthode d'extension Simple pour copier toutes les propriétés publiques. Les œuvres pour tous les objets et ne doit pas être Classe être [Serializable] . Peut être étendu à d'autres niveaux 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 ) } );
        }
    };
}
35
répondu Konstantin Salavatov 2016-04-07 12:59:49

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

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

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

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

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

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

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}
28
répondu Michael White 2017-03-21 14:11:33

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 Iserialisable ou ICloneable sur vos objets. C'est commun avec le modèle MVC/MVVM, donc des outils simples comme celui-ci ont été créés.

voir la valueinjecter profonde clonage solution sur CodePlex .

26
répondu Michael Cox 2016-09-19 22:57:37

je viens de créer CloneExtensions bibliothèque projet. Il effectue des clones rapides et profonds en utilisant des opérations d'affectation simples générées par la compilation de code D'exécution D'arbre D'Expression.

Comment l'utiliser?

au lieu d'écrire vos propres méthodes Clone ou Copy avec un ton d'attributions entre les champs et les propriétés faire le programme le faire pour toi-même, en utilisant L'arbre D'Expression. La méthode GetClone<T>() marquée en tant que méthode d'extension vous permet simplement de l'appeler sur votre instance:

var newInstance = source.GetClone();

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

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

que peut-on cloner?

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

les membres suivants de la classe/structure sont clonés à l'intérieur:

  • Valeurs de public, pas en lecture seule champs
  • valeurs des propriétés publiques avec des accesseurs get et set
  • les articles de la Collection pour les types de mise en œuvre de ICollection

Comment il est rapide?

la solution est plus rapide que la réflexion, parce que les informations des membres doivent être recueillies une seule fois, avant que GetClone<T> ne soit utilisé pour la première fois pour un type donné T .

c'est aussi plus rapide que la solution basée sur la sérialisation quand vous clonez plus que quelques instances de le même type T .

et plus...

plus d'informations sur les expressions générées sur documentation .

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

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


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

}

ce qui a le même sens comme 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 comme la façon dont vous écririez votre méthode Clone pour List<int> ?

25
répondu MarcinJuraszek 2015-09-08 15:26:38

la réponse courte est que vous héritez de l'interface ICloneable et puis mettre en œuvre le .clone de la fonction. Clone doit faire une copie par membre et effectuer une copie profonde sur tout membre qui l'exige, puis retourner l'objet résultant. Il s'agit d'une opération récursive ( il faut que tous les membres de la classe que vous voulez cloner soient des types de valeurs ou implémentent ICloneable et que leurs membres soient des types de valeurs ou implémentent ICloneable, et ainsi de suite).

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

Le long la réponse est "ça dépend". Tel que mentionné par d'autres, ICloneable n'est pas appuyé par des génériques, nécessite des considérations spéciales pour les références de classe circulaire, et est en fait considéré par certains comme une "erreur" dans le cadre .NET. La méthode de sérialisation dépend de vos objets sérialisables, qu'ils peuvent pas être et vous n'avez pas le contrôle. La question de savoir quelle est la "meilleure" pratique fait encore l'objet de nombreux débats au sein de la communauté. En réalité, aucune des solutions ne correspond à la pratique exemplaire unique pour toutes les situations comme ICloneable a été interprété à l'origine pour être.

voir ce Developer's Corner article pour plus d'options (crédit à Ian).

20
répondu Zach Burlingame 2018-04-09 22:46:07

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

c'est le clonage basé sur l'expression qui fonctionne environ 10 fois plus vite que la sérialisation binaire et le maintien de l'intégrité du graphe objet complet.

cela signifie: Si vous vous référez plusieurs fois au même objet dans votre hiérachie, le clone aura aussi une instance unique référencée.

pas besoin de interfaces, attributs ou toute autre modification des objets clonés.

15
répondu Michael Sander 2015-08-12 20:07:36

le mieux est de mettre en œuvre une méthode d'extension comme

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

et ensuite l'utiliser n'importe où dans la solution par

var copy = anyObject.DeepClone();

nous pouvons avoir les trois implémentations suivantes:

  1. par sérialisation (code le plus court)
  2. Par Réflexion - 5x plus rapide
  3. par Arbres D'Expression - 20x faster

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

15
répondu frakon 2017-05-23 11:55:19
  1. fondamentalement, vous devez mettre en œuvre l'interface ICloneable et ensuite réaliser la copie de structure d'objet.
  2. S'il s'agit d'une copie profonde de tous les membres, vous devez vous assurer (Non relating on solution que vous choisissez) que tous les enfants sont clonable ainsi.
  3. parfois, vous devez être conscient d'une certaine restriction au cours de ce processus, par exemple si vous copiez les objets ORM la plupart des cadres ne permettent qu'un seul objet attaché à la session et vous Ne doit pas faire de clones de cet objet, ou s'il est possible que vous devez vous soucier de session attachant de ces objets.

santé.

15
répondu dimarzionist 2017-07-03 07:25:13

je suis venu avec ceci pour surmonter un .NET défaut de copier manuellement la liste profonde.

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, en raison de rendement de ne pas travailler à l'intérieur des blocs de méthode anonyme.

mieux encore, utilisez la liste générique cloner:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}
10
répondu Daniel Mošmondor 2012-12-03 17:23:13

Garder les choses simples et des AutoMapper comme d'autres l'ont mentionné, c'est une simple petite bibliothèque à la carte un objet à un autre... Pour copier un objet à un autre avec le même type, il 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;
}

par la méthode de l'extension, les trois lignes deviennent une ligne:

MyType copy = source.Copy();
9
répondu Stacked 2016-05-28 11:23:05

en général, vous implémentez L'interface ICloneable et implémentez Clone vous-même. C# objets ont un MemberwiseClone méthode qui effectue une copie superficielle qui peut vous aider pour toutes les primitives.

pour une copie profonde, il n'y a pas moyen qu'il sache le faire automatiquement.

7
répondu HappyDude 2008-09-17 00:09:02

Je l'ai vu mise en œuvre par la réflexion aussi. Fondamentalement, il y avait une méthode qui itérait à travers les membres d'un objet et les copiait de manière appropriée au 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 est chère, mais ça a plutôt bien marché.

7
répondu xr280xr 2010-10-19 13:01:19

Voici une mise en œuvre en profondeur:

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;
}
7
répondu dougajmcdonald 2011-09-06 07:38:32

Q. Pourquoi choisirais-je cette réponse?

  • choisissez cette réponse si vous voulez la vitesse la plus rapide.net est capable de.
  • ignorez cette réponse si vous voulez une méthode vraiment, vraiment facile de Clonage.

en d'autres termes, aller avec une autre réponse à moins que vous avez un goulot d'étranglement de performance qui doit être réparé, et vous pouvez le prouver avec un profileur .

10x plus rapide que d'autres méthodes

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

  • 10x plus rapide que tout ce qui implique la sérialisation/désérialisation;
  • assez proche de la vitesse maximale théorique.

et la méthode ...

pour la vitesse ultime, vous pouvez utiliser membre Imbriquéwiseclone pour faire une copie profonde . Sa vitesse est presque la même que celle de copier une structure de valeur, et est beaucoup plus rapide que (a) réflexion ou (b) sérialisation (comme décrit dans d'autres réponses sur cette page).

notez que si vous utilisez Memberwiseclone imbriqué pour une copie profonde , vous devez implémenter manuellement une ShallowCopy pour chaque niveau imbriqué dans la classe, et une DeepCopy qui appelle toutes ces méthodes de 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 Imbriquée MemberwiseClone sur imbriqués les structures
  • 4.77 secondes pour les membres imbriqués en classe imbriquée
  • 39.93 secondes pour la Sérialisation/Désérialisation

Utilisant Nested MemberwiseClone sur une classe presque aussi vite que Copier une struct, et copier une struct est assez proche de la vitesse maximale théorique.net en 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 profonde en utilisant 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;
    }
}

alors, appelez la démo de main:

void MyMain(string[] args)
{
    {
        Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
        var Bob = new Person(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {               
        Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
        var Bob = new PersonStruct(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details:\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {
        Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
        int total = 0;
        var sw = new Stopwatch();
        sw.Start();
        var Bob = new Person(30, "Lamborghini");
        for (int i = 0; i < 100000; i++)
        {
            var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
            total += BobsSon.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
    }
    Console.ReadKey();
}

encore une fois, notez que si vous utilisez Memberwiseclone imbriqué pour une copie profonde , vous devez implémenter manuellement une ShallowCopy pour chaque niveau imbriqué dans la classe, et une DeepCopy qui appelle toutes les méthodes dites 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 et types de références

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

  • si vous avez un" struct ", c'est un type de valeur ainsi vous pouvez simplement le copier, et le contenu sera cloné (mais il ne fera un clone peu profond sauf si vous utilisez les techniques dans ce post).
  • Si vous avez un " classe ", c'est un type de référence , donc si vous le copiez, tous les vous êtes en train de faire est de copier le pointeur. Pour créer un vrai clone, vous devez être plus créatif, et utiliser différences entre les types de valeurs et les types de références qui crée une autre copie de l'objet original en mémoire.

voir différences entre les types de valeurs et les types de références .

Checksums pour aider au débogage

  • Clonage d'objets une erreur peut conduire à des bogues très difficiles à cerner. Dans le code de production, j'ai tendance à implémenter un checksum pour vérifier deux fois que l'objet a été cloné correctement, et n'a pas été corrompu par une autre référence à lui. Cette somme de contrôle peut être désactivée en mode Release.
  • je trouve cette méthode très utile: souvent, on ne veut cloner que des parties de l'objet, pas la totalité.

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

un excellent cas d'utilisation pour ce code est l'alimentation des clones d'une classe imbriquée ou d'une structure dans une file d'attente, pour mettre en œuvre le modèle producteur / consommateur.

  • nous pouvons avoir un (ou plusieurs) threads modifiant une classe qu'ils possèdent, puis poussant une copie complète de cette classe dans un ConcurrentQueue .
  • nous avons alors un (ou plusieurs) threads tirant des copies de ces classes et traitant avec eux.

cela fonctionne extrêmement bien dans la pratique, et nous permet de découpler de nombreux fils (les producteurs) à partir d'un ou plusieurs fils (les consommateurs).

et cette méthode est aussi incroyablement rapide: si nous utilisons des structures imbriquées, c'est 35 fois plus rapide que la sérialisation/désérialisation des classes imbriquées, et cela nous permet de profiter de tous les threads disponibles sur la machine.

mise à Jour

apparemment, ExpressMapper est aussi vite, voire plus vite, que le codage à la main comme ci-dessus. Je devrais peut-être voir comment ils se comparent à un profileur.

7
répondu Contango 2015-09-18 18:12:33

comme je n'ai pas pu trouver de cloner qui réponde à toutes mes exigences dans différents projets, j'ai créé un cloner profond qui peut être configuré et adapté à différentes structures de code au lieu d'adapter mon code pour répondre aux exigences des cloners. C'est obtenu en ajoutant des annotations au code qui doit être cloné ou vous laissez simplement le code tel qu'il est pour avoir le comportement par défaut. Il utilise la réflexion, les caches de type et est basé sur fasterflect . Le processus de clonage est très rapide pour une énorme quantité de données et une haute hiérarchie d'objets (par rapport à d'autres algorithmes basés sur la réflexion/sérialisation).

https://github.com/kalisohn/CloneBehave

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

par exemple: le code suivant va deepClone Address, mais seulement effectuer 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
répondu kalisohn 2016-01-27 12:53:06

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
répondu JerryGoyal 2016-04-12 13:43:36

j'aime les Copyconstructeurs 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 d'autres choses à copier, ajoutez-les

5
répondu LuckyLikey 2015-03-06 15:39:15

Générateur De Code

nous avons vu beaucoup d'idées de la sérialisation sur l'implémentation manuelle à la réflexion et je veux proposer une approche totalement différente en utilisant le générateur de code CGbR . La méthode generate clone est efficace en mémoire et CPU et donc 300x plus rapide que le DataContractSerializer standard.

Tout ce dont vous avez besoin est une définition partielle de classe 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;
    }
}

Note: la dernière version comporte davantage de vérifications nulles, mais je les ai laissées de côté pour mieux les comprendre.

5
répondu Toxantron 2016-06-09 21:24:27

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 : exige

    using System.Linq;
    using System.Reflection;

C'est la Façon dont je l'ai utilisé

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
5
répondu Daniele D. 2016-07-29 14:15:45

suivez ces étapes:

  • définit un ISelf<T> avec un bien en lecture seule Self qui retourne T , et ICloneable<out T> , qui dérive de ISelf<T> et comprend une méthode T Clone() .
  • définit alors un type CloneBase qui implémente un protected virtual generic VirtualClone coulage MemberwiseClone au type passé.
  • chaque type dérivé devrait implémenter VirtualClone en appelant la base méthode du clone et ensuite faire tout ce qui doit être fait pour bien cloner les aspects du type dérivé que la méthode du VirtualClone parent n'a pas encore manipulés.

pour une polyvalence d'héritage maximale, les classes exposant la fonctionnalité de Clonage public devraient ê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 s'attend à ce qu'un dérivé clonable de Foo fonctionne avec un dérivé clonable de DerivedFoo , mais permettra également la création de dérivés non clonables de Foo .

4
répondu supercat 2013-06-04 14:29:45

je pense que vous pouvez essayer.

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
4
répondu Sudhanva Kotabagi 2016-08-19 16:47:21

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

Exige Système De Système De.IO, Système.Runtime.La Sérialisation, Système.Runtime.Sérialisation.Formateuses.Binaire, Système.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 /q/deep-cloning-objects-7881/"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;
    }

} 
3
répondu Jeroen Ritmeijer 2014-04-11 16:06:55

Pour cloner votre objet de classe, vous pouvez utiliser l'Objet.Méthode MemberwiseClone,

il suffit d'ajouter cette fonction à votre classe :

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

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

alors pour effectuer une copie indépendante profonde, il suffit d'appeler la méthode de la copie profonde:

yourClass newLine = oldLine.DeepCopy();

espérons que cette aide.

3
répondu Chtiwi Malek 2014-04-25 09:39:22

Ok, il y a un exemple évident avec réflexion dans ce post, mais la réflexion est généralement lente, jusqu'à ce que vous commenciez à le mettre en cache correctement.

si vous le cachez correctement, qu'il va cloner profond objet 1000000 par 4,6 s (mesurée par Watcher).

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

que vous prenez des propriétés cachées ou ajouter nouveau au dictionnaire et les utiliser simplement

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

contrôle de code complet dans mon poste dans une autre réponse

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

3
répondu Roma Borodov 2017-05-23 11:47:36

si votre arbre D'objets est sérialisable, vous pouvez aussi utiliser quelque chose comme ça

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

soyez informé que cette Solution est assez facile, mais elle n'est pas aussi performante que d'autres solutions peuvent l'être.

et assurez-vous que si la classe grandit, il n'y aura plus que les champs clonés, qui seront aussi sérialisés.

3
répondu LuckyLikey 2017-03-21 14:14:04