Frage

    

Diese Frage bereits eine Antwort hier:

    
            
  •              Tief Klonen Objekte                                      43 Antworten                          
  •     
    

Ich möchte eine echte tiefe Kopie. In Java, das war einfach, aber wie wollen Sie tun es in C #?

War es hilfreich?

Lösung

Ich habe ein paar verschiedenen Ansätze gesehen, aber ich benutze eine allgemeine Hilfsmethode als solche:

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

Weitere Informationen:

  • Ihre Klasse muss als [Serializable] gekennzeichnet werden, damit dies funktioniert.
  • Ihre Quelldatei muss den folgenden Code enthalten:

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

Andere Tipps

schrieb eine tiefe Objektkopie-Extension-Methode , basierend auf rekursive "MemberwiseClone" . Es ist schnell ( dreimal schneller als BinaryFormatter), und es funktioniert mit jedem Objekt. Sie brauchen keinen Standardkonstruktor oder serializable Attribute müssen.

Der Quellcode:

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

}

Aufbauend auf Kilhoffer Lösung ...

Mit C # 3.0 können Sie eine Erweiterungsmethode wie folgt erstellen:

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

, die sie jede Klasse, die als [Serializable] markiert ist mit einer DeepClone Methode

MyClass copy = obj.DeepClone();

Sie können mit Verschachtelte MemberwiseClone eine tiefe Kopie tun . Es ist fast die gleiche Geschwindigkeit wie Wert struct Kopieren und dessen eine Größenordnung schneller als (a) Reflexion oder (b) die Serialisierung (wie in anderen Antworten auf dieser Seite beschrieben).

Beachten Sie, dass , wenn Sie verwenden Verschachtelte MemberwiseClone für eine tiefe Kopie , müssen Sie manuell eine shallowCopy für jede verschachtelte Ebene in der Klasse implementieren und ein deep, die alle Anrufe sagte shallowCopy Methoden, um einen vollständigen Klon zu erstellen. Das ist ganz einfach. Nur ein paar Zeilen insgesamt unter dem Demo-Code finden Sie unter

Hier ist die Ausgabe des Codes des relativen Performance-Unterschied (4,77 Sekunden für tief verschachtelte MemberwiseCopy vs. 39,93 Sekunden für Serialisierung) zeigt. mit geschachtelten MemberwiseCopy ist fast so schnell wie eine Struktur zu kopieren, und eine Struktur zu kopieren ist verflixt nahe der theoretischen .NET maximalen Geschwindigkeit ist in der Lage, die wahrscheinlich auf die Geschwindigkeit von der gleichen Sache in C oder C ++ ganz in der Nähe ist (aber würde haben einige gleichwertige Benchmarks laufen diese Behauptung zu überprüfen).

    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

Um zu verstehen, wie eine tiefe Kopie zu tun MemberwiseCopy verwenden, hier ist das Demo-Projekt:

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

Dann rufen Sie die Demo aus dem Haupt:

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

Auch Anmerkung, dass , wenn verwenden Sie Verschachtelte MemberwiseClone für eine tiefe Kopie , müssen Sie manuell eine shallowCopy für jede verschachtelte Ebene in der Klasse implementieren und einem deep der fordert die alle shallowCopy Methoden einen vollständigen Klon zu erstellen. Das ist ganz einfach. Nur ein paar Zeilen insgesamt, über die Demo-Code finden Sie unter

Beachten Sie, dass, wenn es um ein Objekt zu klonen kommt, gibt es einen großen Unterschied zwischen einer „Struktur“ und „Klasse“:

  • Wenn Sie eine „Struktur“ haben, es ist ein Werttyp, so dass Sie es einfach kopieren können, und der Inhalt geklont werden.
  • Wenn Sie eine „Klasse“ haben, ist es ein Referenztyp, also wenn Sie es kopieren, alles, was Sie tun, ist das Kopieren der Zeiger darauf. Um einen echten Klon zu erstellen, haben Sie mehr kreativ zu sein, und verwenden Sie eine Methode, die eine weitere Kopie des ursprünglichen Objekts im Speicher erstellt.
  • Cloning-Objekte können falsch zu sehr schwer zu pin-down Bugs führen. In Produktionscode, neige ich dazu, eine Prüfsumme zu überprüfen implementieren, dass das Objekt richtig geklont wurde, und wurde nicht durch einen anderen Hinweis darauf beschädigt. Diese Prüfsumme kann in Release-Modus ausgeschaltet werden.
  • Ich finde diese Methode sehr nützlich: oft, dass Sie nur wollen Teile des Objekts klonen, nicht die ganze Sache. Es ist auch wichtig für die weitere Verwendung Fall, in dem Sie Objekte ändern, und die modifizierte Kopien in eine Warteschlange zugeführt wird.

Aktualisieren

Es ist wahrscheinlich möglich Reflektion zu verwenden rekursiv durch den Objektgraphen zu Fuß eine tiefe Kopie zu tun. WCF verwendet diese Technik ein Objekt zu serialisiert, alle seine Kinder mit. Der Trick ist, alle untergeordneten Objekte mit einem Attribut mit Anmerkungen zu versehen, die es auffindbar macht. Sie könnten einige Leistungsvorteile verlieren, aber.

Aktualisieren

Zitat auf unabhängigem Geschwindigkeitstest (siehe Kommentar unten):

  

Ich habe meine eigene Geschwindigkeit Test mit Neils serialize / deserialize laufen   Erweiterungsmethode, Contango des Nested MemberwiseClone, Alex Burtsev der   Reflexion basierte Erweiterungsmethode und AutoMapper, 1 Million Mal   jeder. Serialisieren-deserialize war langsamste unter 15,7 Sekunden. Dann   AutoMapper kam unter 10,1 Sekunden. Viel schneller war die   Reflexion basierende Methode, die 2,4 Sekunden dauerte. Bei weitem war die schnellste   Verschachtelte MemberwiseClone unter 0,1 Sekunden. Kommt auf die Leistung   gegen lästiges Code für jede Klasse von Zugabe zu klonen. wenn die Leistung   ein Thema geht nicht mit Alex Burtsev Methode.   - Simon Tewsi

Ich glaube, dass die BinaryFormatter Ansatz relativ langsam ist (was als eine Überraschung zu mir kam!). Möglicherweise können protobuf .NET für einige Objekte verwenden, wenn sie die Anforderungen von protobuf erfüllen. Von der protobuf Seite Erste Schritte ( http://code.google.com/p/ protobuf-net / wiki / Gettingstarted ):

Hinweise zu Typen unterstützt:

Benutzerdefinierte Klassen, die:

  • als Daten-Vertrag markiert
  • Haben Sie einen parameterlosen Konstruktor
  • Für Silverlight: sind öffentlich
  • Viele gängige Primitiven, etc.
  • Single Dimension-Arrays: T []
  • Liste / IList
  • Wörterbuch / IDictionary
  • jede Art, die IEnumerable implementiert und hat ein Add (T) Methode

Der Code geht davon aus, dass Typen um die gewählten Mitglieder wandelbar sein. Dementsprechend benutzerdefinierte structs nicht unterstützt werden, da sie unveränderlich sein sollte.

Wenn Ihre Klasse diese Anforderungen erfüllt, könnten Sie versuchen:

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

das ist sehr schnell in der Tat ...

Edit:

Hier arbeitet Code für eine Änderung dieser (getestet auf .NET 4.6). Es verwendet System.Xml.Serialization und System.IO. Keine Notwendigkeit zu markieren Klassen als serialisierbar.

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

Sie können versuchen, diese

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

Dank DetoX83 Artikel auf Code Projekt.

Vielleicht brauchen Sie nur eine flache Kopie, in diesem Fall der Verwendung Object.MemberWiseClone().

Es gibt gute Empfehlungen in der Dokumentation zu MemberWiseClone() für Strategien tiefe Kopie: -

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

Der beste Weg ist:

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

Die MSDN-Dokumentation scheint anzudeuten, dass Clone eine tiefe Kopie durchführen sollten, aber es wird nie explizit angegeben:

Die ICloneable Schnittstelle ein Mitglied, Klon enthält, die dazu bestimmt ist, daß durch die Klonierung über MemberwiseClone zuzuführenden zu unterstützen ... Die MemberwiseClone Methode erzeugt eine flache Kopie ...

Sie können meinen Beitrag hilfreich.

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

Auf diese Weise ist ein paar Mal schneller als BinarySerialization und dies erfordert nicht das [Serializable] Attribut.

Ich habe eine einfachere Idee. Verwenden Sie LINQ mit einer neuen Auswahl.

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};
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top