Domanda

    

Questa domanda ha già una risposta qui:

    
            
  •              Oggetti di clonazione profonda                                      43 risposte                          
  •     
    

Voglio una vera copia profonda. In Java, questo è stato facile, ma come si fa in C #?

È stato utile?

Soluzione

Ho visto alcuni approcci diversi a questo, ma utilizzo un metodo di utilità generico in quanto tale:

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

Note:

  • La tua classe DEVE essere contrassegnata come [Serializable] affinché funzioni.
  • Il tuo file sorgente deve includere il seguente codice:

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

Altri suggerimenti

I ho scritto un metodo di estensione della copia di oggetti profondi , basato su ricorsivo quot &; MemberwiseClone quot &; . È veloce ( tre volte più veloce di BinaryFormatter) e funziona con qualsiasi oggetto. Non hai bisogno di un costruttore predefinito o di attributi serializzabili.

Codice sorgente:

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

}

Basandosi sulla soluzione di Kilhoffer ...

Con C # 3.0 è possibile creare un metodo di estensione come segue:

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

che estende qualsiasi classe contrassegnata come [Serializable] con un metodo DeepClone

MyClass copy = obj.DeepClone();

Puoi utilizzare Nested MemberwiseClone per eseguire una copia approfondita . È quasi la stessa velocità della copia di una struttura di valori ed è un ordine di grandezza più veloce di (a) riflessione o (b) serializzazione (come descritto in altre risposte in questa pagina).

Nota che se usi Nwise MemberwiseClone per una copia profonda , devi implementare manualmente uno ShallowCopy per ogni livello nidificato nella classe e un DeepCopy che chiama tutti detto metodi ShallowCopy per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo di seguito.

Ecco l'output del codice che mostra la differenza relativa delle prestazioni (4,77 secondi per MemberwiseCopy annidato in profondità rispetto a 39,93 secondi per la serializzazione). L'utilizzo di MemberwiseCopy nidificato è quasi rapido quanto la copia di una struttura e la copia di una struttura è quasi dannatamente vicina alla velocità massima teorica di cui è capace .NET, che è probabilmente abbastanza vicino alla velocità della stessa cosa in C o C ++ (ma devono eseguire alcuni benchmark equivalenti per verificare questa affermazione).

    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

Per capire come fare una copia profonda usando MemberwiseCopy, ecco il progetto demo:

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

Quindi, chiama la demo da 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();
    }

Ancora una volta, si noti che se si utilizza Nested MemberwiseClone per una copia profonda , è necessario implementare manualmente una ShallowCopy per ogni livello nidificato nella classe e una DeepCopy che chiama tutti i metodi ShallowCopy detti per creare un clone completo. Questo è semplice: solo poche righe in totale, vedi il codice demo sopra.

Nota che quando si tratta di clonare un oggetto, c'è una grande differenza tra un " struct " e una " class " ;:

  • Se hai un " struct " ;, è un tipo di valore, quindi puoi semplicemente copiarlo e il contenuto verrà clonato.
  • Se hai un " class " ;, è un tipo di riferimento, quindi se lo copi, tutto ciò che stai facendo è copiare il puntatore ad esso. Per creare un vero clone, devi essere più creativo e utilizzare un metodo che crea un'altra copia dell'oggetto originale in memoria.
  • La clonazione errata di oggetti può portare a bug molto difficili da individuare. Nel codice di produzione, tendo a implementare un checksum per verificare che l'oggetto sia stato clonato correttamente e non sia stato danneggiato da un altro riferimento ad esso. Questo checksum può essere disattivato in modalità di rilascio.
  • Trovo questo metodo abbastanza utile: spesso vuoi solo clonare parti dell'oggetto, non l'intera cosa. È inoltre essenziale per qualsiasi caso d'uso in cui si stanno modificando oggetti, quindi inserendo le copie modificate in una coda.

Aggiorna

Probabilmente è possibile usare la riflessione per camminare ricorsivamente attraverso il grafico degli oggetti per fare una copia profonda. WCF utilizza questa tecnica per serializzare un oggetto, inclusi tutti i suoi figli. Il trucco è annotare tutti gli oggetti figlio con un attributo che lo rende rilevabile. Tuttavia, potresti perdere alcuni vantaggi in termini di prestazioni.

Aggiorna

Citazione su test di velocità indipendente (vedi commenti sotto):

  

Ho eseguito il mio test di velocità usando la serializzazione / deserializzazione di Neil   metodo di estensione, Nwise MemberwiseClone di Contango, Alex Burtsev   metodo di estensione basato sulla riflessione e AutoMapper, 1 milione di volte   ogni. Serialize-deserialize è stato il più lento, impiegando 15,7 secondi. Poi   è arrivato AutoMapper, impiegando 10,1 secondi. Molto più veloce è stato il   metodo basato sulla riflessione che impiegava 2,4 secondi. Di gran lunga il più veloce è stato   MemberwiseClone nidificato, impiegando 0,1 secondi. Si riduce alle prestazioni   contro la seccatura di aggiungere codice a ogni classe per clonarlo. Se prestazioni   non è un problema andare con il metodo di Alex Burtsev.   & # 8211; Simon Tewsi

Credo che l'approccio BinaryFormatter sia relativamente lento (il che mi ha sorpreso!). È possibile utilizzare ProtoBuf .NET per alcuni oggetti se soddisfano i requisiti di ProtoBuf. Dalla pagina ProtoBuf per iniziare ( http://code.google.com/p/ protobuf-net / wiki / GettingStarted):

Note sui tipi supportati:

Classi personalizzate che:

  • Sono contrassegnati come contratto dati
  • Avere un costruttore senza parametri
  • Per Silverlight: sono pubblici
  • Molti primitivi comuni, ecc.
  • Array di dimensioni
  • singole : T []
  • &
  • Lista lt; T gt &; / IList & Lt; T & Gt;
  • Dizionario < TKey, TValue > / IDictionary & Lt; TKey, TValue & Gt;
  • qualsiasi tipo che implementa IEnumerable < T > e ha un metodo Add (T)

Il codice presuppone che i tipi saranno mutabili attorno ai membri eletti. Di conseguenza, le strutture personalizzate non sono supportate, poiché dovrebbero essere immutabili.

Se la tua classe soddisfa questi requisiti puoi provare:

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

Che è MOLTO veloce davvero ...

Modifica

Ecco il codice di lavoro per una modifica di questo (testato su .NET 4.6). Utilizza System.Xml.Serialization e System.IO. Non è necessario contrassegnare le classi come serializzabili.

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

Puoi provare

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

Grazie a DetoX83 articolo sul codice progetto.

Forse hai solo bisogno di una copia superficiale, in quel caso usa Object.MemberWiseClone().

Ci sono buoni consigli nella documentazione per MemberWiseClone() per le strategie di copia profonda: -

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

Il modo migliore è:

    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 documentazione MSDN sembra suggerire che Clone dovrebbe eseguire una copia approfondita, ma non è mai esplicitamente dichiarato:

L'interfaccia ICloneable contiene un membro, Clone, che intende supportare la clonazione oltre a quella fornita da MemberWiseClone & # 8230; Il metodo MemberwiseClone crea una copia superficiale & # 8230;

Puoi trovare utile il mio post.

http://pragmaticcoding.com/index.php/cloning-objects -in-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;
        }
    }

In questo modo è alcune volte più veloce di BinarySerialization E questo non richiede l'attributo [Serializable].

Ho un'idea più semplice. Usa LINQ con una nuova selezione.

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};
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top