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

StackOverflow https://stackoverflow.com/questions/129389

  •  02-07-2019
  •  | 
  •  

Question

    

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

    
            
  •              Objets de clonage en profondeur                                      43 réponses                          
  •     
    

Je veux une vraie copie en profondeur. En Java, c’était simple, mais comment le faire en C #?

Était-ce utile?

La solution

J'ai déjà vu différentes approches, 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);
 }
}

Notes:

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

Autres conseils

J'ai écrit une méthode d'extension de copie d'objet profond , basée sur une méthode récursive. & "MemberwiseClone" & "; . Il est rapide ( trois fois plus rapide que BinaryFormatter) et fonctionne avec n’importe quel objet. Vous n'avez pas besoin d'un constructeur par défaut ou d'attributs sérialisables.

Code source:

using System.Collections.Generic;
using System.Reflection;
using System.ArrayExtensions;

namespace System
{
    public static class ObjectExtensions
    {
        private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);

        public static bool IsPrimitive(this Type type)
        {
            if (type == typeof(String)) return true;
            return (type.IsValueType & type.IsPrimitive);
        }

        public static Object Copy(this Object originalObject)
        {
            return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
        }
        private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
        {
            if (originalObject == null) return null;
            var typeToReflect = originalObject.GetType();
            if (IsPrimitive(typeToReflect)) return originalObject;
            if (visited.ContainsKey(originalObject)) return visited[originalObject];
            if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
            var cloneObject = CloneMethod.Invoke(originalObject, null);
            if (typeToReflect.IsArray)
            {
                var arrayType = typeToReflect.GetElementType();
                if (IsPrimitive(arrayType) == false)
                {
                    Array clonedArray = (Array)cloneObject;
                    clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
                }

            }
            visited.Add(originalObject, cloneObject);
            CopyFields(originalObject, visited, cloneObject, typeToReflect);
            RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
            return cloneObject;
        }

        private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
        {
            if (typeToReflect.BaseType != null)
            {
                RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
                CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
            }
        }

        private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
        {
            foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
            {
                if (filter != null && filter(fieldInfo) == false) continue;
                if (IsPrimitive(fieldInfo.FieldType)) continue;
                var originalFieldValue = fieldInfo.GetValue(originalObject);
                var clonedFieldValue = InternalCopy(originalFieldValue, visited);
                fieldInfo.SetValue(cloneObject, clonedFieldValue);
            }
        }
        public static T Copy<T>(this T original)
        {
            return (T)Copy((Object)original);
        }
    }

    public class ReferenceEqualityComparer : EqualityComparer<Object>
    {
        public override bool Equals(object x, object y)
        {
            return ReferenceEquals(x, y);
        }
        public override int GetHashCode(object obj)
        {
            if (obj == null) return 0;
            return obj.GetHashCode();
        }
    }

    namespace ArrayExtensions
    {
        public static class ArrayExtensions
        {
            public static void ForEach(this Array array, Action<Array, int[]> action)
            {
                if (array.LongLength == 0) return;
                ArrayTraverse walker = new ArrayTraverse(array);
                do action(array, walker.Position);
                while (walker.Step());
            }
        }

        internal class ArrayTraverse
        {
            public int[] Position;
            private int[] maxLengths;

            public ArrayTraverse(Array array)
            {
                maxLengths = new int[array.Rank];
                for (int i = 0; i < array.Rank; ++i)
                {
                    maxLengths[i] = array.GetLength(i) - 1;
                }
                Position = new int[array.Rank];
            }

            public bool Step()
            {
                for (int i = 0; i < Position.Length; ++i)
                {
                    if (Position[i] < maxLengths[i])
                    {
                        Position[i]++;
                        for (int j = 0; j < i; j++)
                        {
                            Position[j] = 0;
                        }
                        return true;
                    }
                }
                return false;
            }
        }
    }

}

Miser 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 marquée en tant que [Serializable] avec une méthode DeepClone

MyClass copy = obj.DeepClone();

Vous pouvez utiliser Nwise MemberwiseClone pour effectuer une copie en profondeur . C'est presque la même vitesse que de copier 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 vous utilisez Nwise MemberwiseClone pour une copie en profondeur , vous devez implémenter manuellement une ShallowCopy pour chaque niveau imbriqué de la classe et une DeepCopy qui appelle tous les utilisateurs. a déclaré les méthodes ShallowCopy pour créer un clone complet. C’est simple: quelques lignes au total, voir le code de démonstration ci-dessous.

Voici la sortie du code indiquant la différence de performance relative (4,77 secondes pour MemberwiseCopy imbriqué profond par rapport à 39,93 secondes pour une sérialisation). MemberwiseCopy imbriqué est presque aussi rapide que de copier une structure, et copier une structure est sacrément proche de la vitesse maximale théorique dont NET est capable, ce qui est probablement assez proche de la vitesse de la même chose en C ou C ++ (mais exécuter des tests équivalents pour vérifier cette affirmation).

    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 effectuer une copie en profondeur à 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 à partir 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 vous utilisez Nested MemberwiseClone pour une copie en profondeur , vous devez implémenter manuellement un ShallowCopy pour chaque niveau imbriqué de la classe et un DeepCopy qui appelle toutes les méthodes dites ShallowCopy pour créer un clone complet. C’est simple: quelques lignes au total, voir le code de démonstration ci-dessus.

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

  • Si vous avez un & "struct &", c'est un type de valeur afin que vous puissiez simplement 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, vous ne faites que copier le pointeur sur celui-ci. 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 conduire à des bogues très difficiles à cerner. Dans le code de production, j'ai tendance à mettre en œuvre une somme de contrôle pour vérifier que l'objet a été correctement cloné et qu'il n'a pas été corrompu par une autre référence. Cette somme de contrôle peut être désactivée en mode Libération.
  • Je trouve cette méthode très utile: vous ne voulez souvent que cloner des parties de l'objet, pas l'intégralité. 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 récursivement le graphe d’objets afin d’en faire une copie profonde. 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. Cependant, vous pourriez perdre certains avantages en termes de performances.

Mettre à jour

Citation sur le test de vitesse indépendant (voir commentaires ci-dessous):

  

J'ai effectué mon propre test de vitesse à l'aide de l'option de 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. La sérialisation-désérialisation a été la plus lente, prenant 15,7 secondes. ensuite   AutoMapper est venu, prenant 10,1 secondes. Beaucoup plus rapide était le   méthode basée sur la réflexion qui a pris 2,4 secondes. De loin le plus rapide était   MemberwiseClone imbriqué, en 0,1 seconde. Revient à la performance   par opposition à l'ajout de code à chaque classe pour le cloner. Si performance   la méthode d'Alex Burtsev n'est pas un problème.   & # 8211; Simon Tewsi

Je pense que l’approche de BinaryFormatter est relativement lente (ce qui m’a surpris!). Vous pourrez peut-être utiliser ProtoBuf .NET pour certains objets s'ils répondent aux exigences de ProtoBuf. Sur la page Mise en route de ProtoBuf ( http://code.google.com/p/ protobuf-net / wiki / GettingStarted ):

Remarques sur les types pris en charge:

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.
  • Tableaux
  • Single : T []
  • Liste < T > / IList & Lt; T & Gt;
  • Dictionnaire < TKey, TValue > / IDictionary & Lt; TKey, TValue & Gt;
  • tout type qui implémente IEnumerable < T > et a une méthode Add (T)

Le code suppose que les types seront mutables autour des membres élus. Par conséquent, les structures personnalisées ne sont pas prises en charge car elles doivent être immuables.

Si votre classe remplit ces conditions, 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 vraiment très rapide ...

Modifier:

Voici un code de travail permettant de le modifier (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);
    }
}

Vous pouvez essayer ceci

    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 code projet.

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

Il existe de bonnes recommandations dans la documentation de MemberWiseClone() stratégies de copie en profondeur: -

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

Le meilleur moyen 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];
    }

La documentation MSDN semble indiquer que Clone devrait effectuer une copie complète, mais cela n’a jamais été explicitement indiqué:

L'interface ICloneable contient un membre, Clone, destiné à prendre en charge le clonage au-delà de celui fourni par MemberWiseClone & # 8230; La méthode MemberwiseClone crée une copie superficielle & # 8230;

Vous pouvez trouver mon message utile.

http://pragmaticcoding.com/index.php/cloning-objects -en-c /

    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 méthode est quelques fois plus rapide que BinarySerialization ET cela ne nécessite pas l'attribut [Serializable].

J'ai une idée plus simple. Utilisez LINQ avec une nouvelle sélection.

public class Fruit
{
  public string Name {get; set;}
  public int SeedCount {get; set;}
}

void SomeMethod()
{
  List<Fruit> originalFruits = new List<Fruit>();
  originalFruits.Add(new Fruit {Name="Apple", SeedCount=10});
  originalFruits.Add(new Fruit {Name="Banana", SeedCount=0});

  //Deep Copy
  List<Fruit> deepCopiedFruits = from f in originalFruits
              select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top