2671 Stimmen

Tiefes Klonen von Objekten

Ich möchte etwas tun wie:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

Und nehmen Sie dann Änderungen am neuen Objekt vor, die sich nicht im ursprünglichen Objekt widerspiegeln.

Ich brauche diese Funktionalität nicht oft, und wenn es nötig war, habe ich ein neues Objekt erstellt und dann jede Eigenschaft einzeln kopiert, aber das hinterlässt bei mir immer das Gefühl, dass es eine bessere oder elegantere Möglichkeit gibt, die Situation zu lösen.

Wie kann ich ein Objekt klonen oder tief kopieren, so dass das geklonte Objekt geändert werden kann, ohne dass sich die Änderungen auf das Originalobjekt auswirken?

112 Stimmen

Kann nützlich sein: "Warum ist das Kopieren eines Objekts eine schreckliche Sache?" agiledeveloper.com/articles/cloning072002.htm

2 Stimmen

stackoverflow.com/questions/8025890/ Eine andere Lösung...

28 Stimmen

Werfen Sie einen Blick auf AutoMapper

2voto

qubits Punkte 1087

Die generischen Ansätze sind alle technisch gültig, aber ich wollte nur eine Anmerkung von mir selbst hinzufügen, da wir selten tatsächlich eine echte tiefe Kopie benötigen, und ich würde stark gegen die Verwendung von generischen tiefes Kopieren in tatsächlichen Geschäftsanwendungen, da das macht es, so dass Sie viele Orte, wo die Objekte kopiert und dann explizit geändert werden, seine leicht zu verlieren haben könnte.

In den meisten realen Situationen möchten Sie auch so viel granulare Kontrolle über den Kopierprozess wie möglich haben, da Sie nicht nur an das Datenzugriffs-Framework gekoppelt sind, sondern auch in der Praxis die kopierten Geschäftsobjekte selten zu 100 % gleich sein sollten. Denken Sie zum Beispiel an die Referenz-IDs, die vom ORM zur Identifizierung von Objektreferenzen verwendet werden. Eine vollständige Deep Copy kopiert auch diese IDs, so dass die Objekte zwar im Speicher unterschiedlich sind, aber sobald Sie sie an den Datenspeicher übermitteln, werden sie sich beschweren, so dass Sie diese Eigenschaften nach dem Kopieren ohnehin manuell ändern müssen.

Erweiternd auf @cregox Antwort mit ICloneable, was ist eigentlich eine tiefe Kopie? Es ist nur ein neu zugewiesenes Objekt auf dem Heap, das mit dem ursprünglichen Objekt identisch ist, aber einen anderen Speicherplatz belegt. Warum also nicht einfach ein neues Objekt erstellen, anstatt eine generische Klon-Funktionalität zu verwenden?

Ich persönlich verwende die Idee der statischen Fabrikmethoden für meine Domänenobjekte.

Beispiel:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

Wenn jemand auf der Suche ist, wie er die Instanziierung von Objekten strukturieren kann, während er die volle Kontrolle über den Kopierprozess behält, ist das eine Lösung, mit der ich persönlich sehr erfolgreich war. Die geschützten Konstruktoren machen es auch möglich, dass andere Entwickler gezwungen sind, die Factory-Methoden zu verwenden, was einen sauberen einzigen Punkt der Objektinstanziierung ergibt, der die Konstruktionslogik innerhalb des Objekts kapselt. Man kann die Methode auch überladen und mehrere Klon-Logiken für verschiedene Stellen haben, falls nötig.

2voto

TarmoPikaro Punkte 4208

Es ist unglaublich, wie viel Aufwand man mit der IClonable-Schnittstelle betreiben kann - vor allem, wenn man große Klassenhierarchien hat. Auch MemberwiseClone funktioniert irgendwie seltsam - es klont nicht einmal normale Strukturen vom Typ Liste.

Und natürlich ist das interessanteste Dilemma für die Serialisierung die Serialisierung von Rückverweisen - z.B. Klassenhierarchien, in denen Sie Kind-Eltern-Beziehungen haben. Ich bezweifle, dass der binäre Serialisierer Ihnen in diesem Fall helfen kann. (Es wird mit rekursiven Schleifen und Stapelüberlauf enden).

Die hier vorgeschlagene Lösung hat mir irgendwie gefallen: Wie macht man eine Deep Copy eines Objekts in .NET (speziell C#)?

jedoch - es unterstützte keine Listen, fügte diese Unterstützung hinzu und berücksichtigte auch die Wiedererziehung. Für Parenting habe ich nur die Regel aufgestellt, dass ein Feld oder eine Eigenschaft "parent" genannt werden sollte, dann wird es von DeepClone ignoriert. Sie können Ihre eigenen Regeln für Rückverweise festlegen - für Baumhierarchien könnte es "links/rechts" sein, usw...

Hier ist ein ganzer Codeschnipsel einschließlich Testcode:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;

            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }

    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name="type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name="obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            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(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child's parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}

2voto

Matthew Watson Punkte 96106

Eine weitere JSON.NET-Antwort. Diese Version funktioniert mit Klassen, die ISerializable nicht implementieren.

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
    }

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}

2voto

LuckyLikey Punkte 3194

Wenn Ihr Objektbaum serialisierbar ist, können Sie auch etwas wie das folgende verwenden

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

seien Sie darüber informiert, dass diese Lösung ziemlich einfach ist, aber nicht so leistungsfähig wie andere Lösungen.

Und stellen Sie sicher, dass, wenn die Klasse wächst, nur noch die Felder geklont werden, die auch serialisiert werden.

1voto

Konrad Punkte 5095

Verwendung von System.Text.Json :

https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/

public static T DeepCopy<T>(this T source)
{
    return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}

Die neue API verwendet Span<T> . Das sollte schnell sein, es wäre schön, ein paar Benchmarks zu machen.

Anmerkung: Es gibt keinen Grund für ObjectCreationHandling.Replace wie in Json.NET, da es standardmäßig Auflistungswerte ersetzen wird. Sie sollten Json.NET jetzt vergessen, da alles durch die neue offizielle API ersetzt werden wird.

Ich bin nicht sicher, ob dies mit privaten Feldern funktioniert.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X