Question Comment faites-vous une copie profonde d'un objet dans .NET (C # spécifiquement)? [dupliquer]


Cette question a déjà une réponse ici:

Je veux une vraie copie profonde. En Java, c'était facile, mais comment le faites-vous en C #?


477
2017-09-24 19:39


origine


Réponses:


J'ai vu plusieurs approches différentes, mais j'utilise une méthode utilitaire générique en tant que telle:

public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

Remarques:

  • Votre classe DOIT être marquée comme [Serializable] pour que cela fonctionne.
  • Votre fichier source doit inclure le code suivant:

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

534
2017-09-24 19:40



je a écrit une méthode d'extension d'objet profonde, basé sur récursif "MemberwiseClone". C'est rapide (trois fois plus vite que BinaryFormatter), et cela fonctionne avec n'importe quel objet. Vous n'avez pas besoin d'un constructeur par défaut ou d'attributs sérialisables.


250
2017-07-03 10:20



Construire sur la solution de Kilhoffer ...

Avec C # 3.0, vous pouvez créer une méthode d'extension comme suit:

public static class ExtensionMethods
{
    // Deep clone
    public static T DeepClone<T>(this T a)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, a);
            stream.Position = 0;
            return (T) formatter.Deserialize(stream);
        }
    }
}

qui étend toute classe qui a été marquée comme [Serializable] avec une méthode DeepClone

MyClass copy = obj.DeepClone();

150
2017-07-31 16:51



Vous pouvez utiliser MemberwiseClone imbriqué pour faire une copie profonde. C'est presque la même vitesse que la copie d'une structure de valeur, et c'est un ordre de grandeur 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 tu utilises 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 ShallowCopy pour créer un clone complet. C'est simple: seulement quelques lignes au total, voir le code de démo ci-dessous.

Voici la sortie du code montrant la différence de performance relative (4,77 secondes pour MemberwiseCopy imbriqué profond contre 39,93 secondes pour la sérialisation). L'utilisation de MemberwiseCopy imbriqué est presque aussi rapide que la copie d'une structure, et copier une structure est très proche de la vitesse maximale théorique .NET est capable de.

    Demo 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 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 of deep copy, using class and serialize/deserialize:
      Elapsed time: 00:00:39.9339425,30000000

Pour comprendre comment faire une copie profonde à l'aide de MemberwiseCopy, voici le projet de démonstration:

// 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 de main:

    void MyMain(string[] args)
    {
        {
            Console.Write("Demo 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", sw.Elapsed, total);
        }
        {               
            Console.Write("Demo 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", sw.Elapsed, total);
        }
        {
            Console.Write("Demo 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 tu utilises 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 ShallowCopy pour créer un clone complet. C'est simple: seulement quelques lignes au total, voir le code de démo ci-dessus.

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 donc vous pouvez juste le copier, et le contenu sera cloné.
  • Si vous avez une "classe", c'est un type de référence, donc si vous le copiez, tout ce que vous faites est de copier le pointeur dessus. Pour créer un vrai clone, vous devez être plus créatif et utiliser une méthode qui crée une autre copie de l'objet original en mémoire.
  • Le 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 Libération.
  • Je trouve cette méthode très utile: souvent, vous voulez seulement cloner des parties de l'objet, pas tout. Il est également essentiel pour tout cas d'utilisation où vous modifiez des objets, puis chargez les copies modifiées dans une file d'attente.

Mettre à jour

Il est probablement possible d'utiliser la réflexion pour parcourir de manière récursive le graphe d'objets afin de faire une copie en profondeur. WCF utilise cette technique pour sérialiser un objet, y compris tous ses enfants. L'astuce consiste à annoter tous les objets enfants avec un attribut qui le rend détectable. Vous pourriez toutefois perdre certains avantages en termes de performances.

Mettre à jour

Citer le test de vitesse indépendant (voir les commentaires ci-dessous):

J'ai effectué mon propre test de vitesse en utilisant la sérialisation / désérialisation de Neil   méthode d'extension, Nested MemberwiseClone de Contango, Alex Burtsev   méthode d'extension basée sur la réflexion et AutoMapper, 1 million de fois   chaque. Sérialiser-désérialiser était le plus lent, prenant 15,7 secondes. alors   est venu AutoMapper, prenant 10,1 secondes. Beaucoup plus vite était le   méthode basée sur la réflexion qui a pris 2,4 secondes. De loin le plus rapide était   MemberwiseClone imbriqué, prenant 0,1 secondes. Descend à la performance   par rapport à la difficulté d'ajouter du code à chaque classe pour le cloner. Si la performance   n'est pas un problème aller avec la méthode d'Alex Burtsev.   - Simon Tewsi


46
2017-12-30 19:05



Je crois que l'approche BinaryFormatter est relativement lente (ce qui m'a surpris!). Vous pourriez être en mesure d'utiliser ProtoBuf .NET pour certains objets s'ils répondent aux exigences de ProtoBuf. À partir de la page ProtoBuf Getting Started (http://code.google.com/p/protobuf-net/wiki/GettingStarted):

Notes sur les types supportés:

Classes personnalisées qui:

  • Sont marqués comme contrat de données
  • Avoir un constructeur sans paramètre
  • Pour Silverlight: sont publics
  • De nombreuses primitives communes, etc.
  • Unique tableaux de dimension: T []
  • Liste <T> / IList <T>
  • Dictionnaire <TKey, TValue> / IDictionary <TKey, TValue>
  • tout type qui implémente IEnumerable <T> et possède une méthode Add (T)

Le code suppose que les types seront mutables autour des membres élus. En conséquence, les structures personnalisées ne sont pas supportées, car elles devraient être immuables.

Si votre classe répond à ces exigences, vous pouvez essayer:

public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        Serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = Serializer.Deserialize<T>(stream);
    }
}

Ce qui est très rapide en effet ...

Modifier:

Voici le code de travail pour une modification de ceci (testé sur .NET 4.6). Il utilise System.Xml.Serialization et System.IO. Pas besoin de marquer les classes comme sérialisables.

public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        var serializer = new XS.XmlSerializer(typeof(T));

        serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = (T)serializer.Deserialize(stream);
    }
}

12
2018-01-03 05:21



Peut-être que vous avez seulement besoin d'une copie superficielle, dans ce cas, utilisez Object.MemberWiseClone().

Il y a de bonnes recommandations dans la documentation pour MemberWiseClone() pour les stratégies de copie profonde: -

http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx


5
2017-12-01 03:00



Tu peux essayer ça

    public static object DeepCopy(object obj)
    {
        if (obj == null)
            return null;
        Type type = obj.GetType();

        if (type.IsValueType || type == typeof(string))
        {
            return obj;
        }
        else if (type.IsArray)
        {
            Type elementType = Type.GetType(
                 type.FullName.Replace("[]", string.Empty));
            var array = obj as Array;
            Array copied = Array.CreateInstance(elementType, array.Length);
            for (int i = 0; i < array.Length; i++)
            {
                copied.SetValue(DeepCopy(array.GetValue(i)), i);
            }
            return Convert.ChangeType(copied, obj.GetType());
        }
        else if (type.IsClass)
        {

            object toret = Activator.CreateInstance(obj.GetType());
            FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                        BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object fieldValue = field.GetValue(obj);
                if (fieldValue == null)
                    continue;
                field.SetValue(toret, DeepCopy(fieldValue));
            }
            return toret;
        }
        else
            throw new ArgumentException("Unknown type");
    }

Merci à DetoX83 article sur le projet de code.


5
2018-04-01 03:59



La meilleure façon est:

    public interface IDeepClonable<T> where T : class
    {
        T DeepClone();
    }

    public class MyObj : IDeepClonable<MyObj>
    {
        public MyObj Clone()
        {
            var myObj = new MyObj();
            myObj._field1 = _field1;//value type
            myObj._field2 = _field2;//value type
            myObj._field3 = _field3;//value type

            if (_child != null)
            {
                myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
            }

            int len = _array.Length;
            myObj._array = new MyObj[len]; // array / collection
            for (int i = 0; i < len; i++)
            {
                myObj._array[i] = _array[i];
            }

            return myObj;
        }

        private bool _field1;
        public bool Field1
        {
            get { return _field1; }
            set { _field1 = value; }
        }

        private int _field2;
        public int Property2
        {
            get { return _field2; }
            set { _field2 = value; }
        }

        private string _field3;
        public string Property3
        {
            get { return _field3; }
            set { _field3 = value; }
        }

        private MyObj _child;
        private MyObj Child
        {
            get { return _child; }
            set { _child = value; }
        }

        private MyObj[] _array = new MyObj[4];
    }

3
2018-04-22 11:23



    public static object CopyObject(object input)
    {
        if (input != null)
        {
            object result = Activator.CreateInstance(input.GetType());
            foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
            {
                if (field.FieldType.GetInterface("IList", false) == null)
                {
                    field.SetValue(result, field.GetValue(input));
                }
                else
                {
                    IList listObject = (IList)field.GetValue(result);
                    if (listObject != null)
                    {
                        foreach (object item in ((IList)field.GetValue(input)))
                        {
                            listObject.Add(CopyObject(item));
                        }
                    }
                }
            }
            return result;
        }
        else
        {
            return null;
        }
    }

Cette façon est quelques fois plus rapide que BinarySerialization ET cela ne nécessite pas la [Serializable] attribut.


1
2017-07-04 13:57