709 votes

Comment faites-vous une copie en profondeur d'un objet dans .Net (C # spécifiquement)?

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

731voto

Kilhoffer Points 13430

J'ai vu quelques approches différentes à cela, mais j'utilise une méthode d'utilité 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;
     

430voto

Alex Burtsev Points 4251

J'ai écrit la méthode d'extension de copie d'objets profonds, basée sur "MemberwiseClone" récursive, elle est rapide ( 3 fois plus rapide que BinaryFormatter), elle fonctionne avec n'importe quel objet, vous n'avez pas besoin de constructeur par défaut ou d'attributs sérialisables.

https://raw.github.com/Burtsev-Alexey/net-object-deep-copy/master/ObjectExtensions.cs

184voto

Neil Points 1859

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

60voto

Contango Points 7976

Vous pouvez utiliser Imbriquée MemberwiseClone faire une copie en profondeur. Ses presque la même vitesse que la copie d'une valeur de struct, et sa un ordre de grandeur plus rapide que (un) ou de réflexion (b) la sérialisation (comme décrit dans d'autres réponses sur cette page).

Notez que si vous utilisez Imbriquée MemberwiseClone pour une copie en profondeur, vous devrez manuellement mettre en œuvre un ShallowCopy pour chaque niveau imbriqué dans la classe, et un Propriétédeepcopy qui appelle tous dit ShallowCopy méthodes pour créer un clone complet. C'est simple: seulement quelques lignes, au total, voir la démo de code ci-dessous.

Voici la sortie du code montrant la performance relative de la différence (4.77 secondes pour profondément imbriquée MemberwiseCopy vs 39.93 secondes pour la Sérialisation). Utilisant des MemberwiseCopy est presque aussi rapide que la copie d'une struct, et la copie d'un struct est vachement proche de la vitesse théorique maximale .NET est capable de faire.

    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 en profondeur à l'aide de MemberwiseCopy, ici, c'est 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 la page principale:

    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 Imbriquée MemberwiseClone pour une copie en profondeur, vous devrez manuellement mettre en œuvre un ShallowCopy pour chaque niveau imbriqué dans la classe, et un Propriétédeepcopy qui appelle tous dit ShallowCopy méthodes pour créer un clone complet. C'est simple: seulement quelques lignes, au total, voir la démo de code ci-dessus.

Notez que quand il s'agit de clonage d'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 de sorte que vous pouvez le copier, et le contenu sera cloné.
  • Si vous avez une "classe", c'est un type de référence, de sorte que, si vous le copiez, tout ce que vous faire est de copier le pointeur. Afin de créer un véritable clone, vous avez à être plus créatifs, et d'utiliser une méthode qui crée une autre copie de l'original de l'objet en mémoire.
  • Le clonage des objets de manière incorrecte peut conduire à de très difficile à pin-bas bugs. Dans le code de production, j'ai tendance à mettre en œuvre une somme de contrôle pour vérifier que l'objet a été cloné correctement, et n'a pas été endommagé par une autre référence. Cette somme de contrôle peut être désactivé en mode Release.
  • Je trouve cette méthode très utile: souvent, vous ne voulez cloner une partie de l'objet, pas l'ensemble de la chose. Il est également essentiel pour n'importe quel cas d'utilisation où vous êtes la modification d'objets, puis de nourrir les copies modifiées dans une file d'attente.

Mise à jour

Il est probablement possible d'utiliser la réflexion de manière récursive à pied à travers l'objet graphique à faire une copie en profondeur. WCF utilise cette technique pour sérialiser un objet, y compris de tous ses enfants. L'astuce consiste à annoter tous les objets enfants avec un attribut qui le rend détectable. Vous risquez de perdre certains avantages de performance, cependant.

21voto

Kurt Richardson Points 51

Je crois que le BinaryFormatter approche est relativement lente (qui est venu comme une surprise pour moi!). Vous pourriez être en mesure d'utiliser ProtoBuf .Net pour certains objets s'ils satisfont aux exigences de ProtoBuf. À partir de la ProtoBuf prise en main page (http://code.google.com/p/protobuf-net/wiki/GettingStarted):

Notes sur les types de prises en charge:

les classes personnalisées:

  • sont marqués comme des données-contrat
  • avoir un constructeur sans paramètre
  • pour Silverlight: public
  • de nombreuses communes primitives etc
  • seule la dimension des tableaux: T[]
  • Liste / IList
  • Dictionnaire / IDictionary
  • n'importe quel type qui implémente l'interface IEnumerable et a un Ajout(T) méthode

Le code suppose que les types mutables autour des membres élus. En conséquence, la coutume des structures ne sont pas pris en charge, car ils doivent être immuables.

Si votre classe répond à ces exigences, vous pourriez 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);
            }

        }

Qui TRÈS vite en effet...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X