Laden...

Tiefes Kopieren belibiger Objektnetze

Erstellt von Floste vor 15 Jahren Letzter Beitrag vor 14 Jahren 9.486 Views
Floste Themenstarter:in
1.130 Beiträge seit 2007
vor 15 Jahren
Tiefes Kopieren belibiger Objektnetze

Beschreibung:

Eine Klasse, mit der man eine tiefe Kopie eines oder mehrerer Objekte und deren referenzierter Objekte anfertigen kann. Ringförmige Verwise werden unterstützt. (Wenn a auf b verweist und b auf a dann verweist hinterher a' auf b' und b' auf a').

Bitte beachten: Beim Erstellen einer Tiefen Kopie in Verbindung mit Netzwerkverbindungen, unmanaged Resourcen, statischen Variablen, u.s.w können unerwartete Effekte und Fehler auftreten. (Keine Singletons oder Windowscontrols kopieren!)

Das Kopieren ist bei der ersten Ausführung etwas langsam, da für jede Klasse (nicht aber für jede Instanz) zuerst eine eigene Kopiermethode erstellt wird.

Zeitmessungen mit recht simplen Konstellationen:
Erstes Dictionary kopieren:13ms
Weitere vom gleichen Typ kopieren je:0,0072ms
Erste Klasseninstanz kopieren: 5,8ms
Weitere Klasseninstanzen vom selben Typ je:0,0062ms

Benutzung:


kopie=DeepCopyer.Copy(original);

using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace DeepCopy
{
    public class DeepCopyer
    {
        public DeepCopyer()
        {
        }

        public object copy(object original)
        {
            if (original == null) return null;
            object result;
            if (!clones.TryGetValue(original, out result))
            {
                result = methods.GetCloneMethod(original.GetType())(this, original);
            }
            return result;
        }

        private void register(object original, object clone)
        {
            clones[original] = clone;
        }

        public static object Copy(object obj)
        {
            DeepCopyer dc = new DeepCopyer();
            return dc.copy(obj);
        }

        public static T Copy<T>(T obj)
        {
            return (T)Copy((object)obj);
        }

        public Dictionary<object, object> clones = new Dictionary<object, object>(64, ReferenceComparer.Default);

        class ReferenceComparer : IEqualityComparer<object>
        {

            protected ReferenceComparer()
            {
            }

            public static readonly ReferenceComparer Default = new ReferenceComparer();

            bool IEqualityComparer<object>.Equals(object x, object y)
            {
                return object.ReferenceEquals(x, y);
            }

            int IEqualityComparer<object>.GetHashCode(object obj)
            {
                return RuntimeHelpers.GetHashCode(obj);
            }
        }

        private static DynamicTypes methods = new DynamicTypes();

        private class DynamicTypes
        {
            public DynamicTypes()
            {
                objMethod[typeof(string)] = new cloneDelegate(strCopy);
            }

            //Weil string nicht alle seine Daten in seinen Feldern speichert, ist eine Sonderbehandlung nötig:
            public static object strCopy(DeepCopyer d, object original)
            {
                object result = ((string)original).Clone();
                d.register(original, result);
                return result;
            }

            private static void IterateArray(DeepCopyer dc, Array array)
            {
                int[] indices = new int[array.Rank];
                int i;
                for (i = 0; i < indices.Length; i++)
                {
                    int lowerBound = array.GetLowerBound(i);
                    indices[i] = lowerBound;
                    if(lowerBound>array.GetUpperBound(i))
                    {
                        return;//Array ist leer
                    }
                }
                i = 0;
                while (i >= 0)
                {
                    array.SetValue(dc.copy(array.GetValue(indices)), indices);
                    i = indices.Length - 1;
                    while (i >= 0)
                    {
                        int ai = indices[i];
                        ai++;
                        if (ai > array.GetUpperBound(i))
                        {
                            indices[i] = array.GetLowerBound(i);
                            i--;
                            continue;
                        }
                        else
                        {
                            indices[i] = ai;
                            break;
                        }
                    }
                }
            }

            Dictionary<Type, cloneDelegate> objMethod = new Dictionary<Type, cloneDelegate>(32);
            MethodInfo memberwiseCloneInfo = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod);
            MethodInfo copyInfo = typeof(DeepCopyer).GetMethod("copy", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod);
            MethodInfo registerInfo = typeof(DeepCopyer).GetMethod("register", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod);
            MethodInfo iterateArrayInfo = typeof(DynamicTypes).GetMethod("IterateArray", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod);
            public delegate object cloneDelegate(DeepCopyer copyer, object original);

            public cloneDelegate GetCloneMethod(Type t)
            {
                cloneDelegate del;
                //Ist Kopiermethode vorhanden? :
                if (!objMethod.TryGetValue(t, out del))
                {
                    //wenn nicht:
                    //erzeuge Methode:
                    DynamicMethod Method = new DynamicMethod("DeepCopyer<>Copy" + t.Name.Replace(".", "<>"),
                        typeof(object), new Type[2] { typeof(DeepCopyer), typeof(object) }, true);

                    ILGenerator gen = Method.GetILGenerator();

                    //object var0;
                    gen.DeclareLocal(typeof(object));
                    //<t> var1;
                    gen.DeclareLocal(t);
                    //object var2;
                    gen.DeclareLocal(typeof(object));

                    //arg1.MemberwiseClone();
                    gen.Emit(OpCodes.Ldarg_1);
                    gen.EmitCall(OpCodes.Call, memberwiseCloneInfo, null);
                    //var0=<letzterRückgabewert>;
                    gen.Emit(OpCodes.Stloc_0);
                    //arg0.register(arg1,var0);
                    gen.Emit(OpCodes.Ldarg_0);
                    gen.Emit(OpCodes.Ldarg_1);
                    gen.Emit(OpCodes.Ldloc_0);
                    gen.EmitCall(OpCodes.Call, registerInfo, null);
                    if (!t.IsArray)
                    {
                        //prüfe, ob Felder vorhanden, die Referenztypen sind:
                        FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                        bool ContainsReferenceTypes = false;
                        foreach (FieldInfo f in fields)
                        {
                            if (!f.FieldType.IsValueType)
                            {
                                ContainsReferenceTypes = true;
                                break;
                            }
                        }
                        if (ContainsReferenceTypes)
                        {
                            //wenn ja:
                            //ersetze referenzierte Objekte durch Kopieen:
                            //var1=(<t>)var0;
                            gen.Emit(OpCodes.Ldloc_0);
                            gen.Emit(OpCodes.Castclass, t);
                            gen.Emit(OpCodes.Stloc_1);
                            foreach (FieldInfo f in fields)
                            {
                                if (!f.FieldType.IsValueType)
                                {
                                    //var2=(object)var1.<f>;
                                    gen.Emit(OpCodes.Ldloc_1);
                                    gen.Emit(OpCodes.Ldfld, f);
                                    gen.Emit(OpCodes.Castclass, typeof(object));
                                    gen.Emit(OpCodes.Stloc_2);

                                    //var2=var0.copy(var2);
                                    gen.Emit(OpCodes.Ldarg_0);
                                    gen.Emit(OpCodes.Ldloc_2);
                                    gen.EmitCall(OpCodes.Call, copyInfo, null);
                                    gen.Emit(OpCodes.Stloc_2);

                                    //var1.<f>=(<f.Type>)var2;
                                    gen.Emit(OpCodes.Ldloc_1);
                                    gen.Emit(OpCodes.Ldloc_2);
                                    gen.Emit(OpCodes.Castclass, f.FieldType);
                                    gen.Emit(OpCodes.Stfld, f);
                                }
                            }
                        }
                    }
                    else
                    {
                        Type elementType = t.GetElementType();
                        if (elementType.IsPrimitive)
                            goto verarbeitet;
                        if (elementType.IsValueType)
                        {
                            FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                            bool ContainsReferenceTypes = false;
                            foreach (FieldInfo f in fields)
                            {
                                if (!f.FieldType.IsValueType)
                                {
                                    ContainsReferenceTypes = true;
                                    break;
                                }
                            }
                            if (!ContainsReferenceTypes)
                                goto verarbeitet;
                        }
                        {
                            //im Array jedes Element auswechseln:
                            gen.Emit(OpCodes.Ldarg_0);
                            gen.Emit(OpCodes.Ldloc_0);
                            gen.Emit(OpCodes.Castclass, typeof(Array));
                            gen.EmitCall(OpCodes.Call, iterateArrayInfo, null);
                        }
                    verarbeitet: { }
                    }
                    //return var0;
                    gen.Emit(OpCodes.Ldloc_0);
                    gen.Emit(OpCodes.Ret);
                    del = (cloneDelegate)Method.CreateDelegate(typeof(cloneDelegate));
                    objMethod[t] = del;
                }
                return del;
            }
        }


    }
}

deep copy tiefe Kopie kopieren baum deepcopy netz

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo floste,

nette Klasse. Ich habe drei kleine Anmerkung und eine Frage.

  1. Die Sonderbehandlung für String, kannst und solltest du etwas einfacher gestalten, weil es nicht nötigt ist Strings zu klonen, weil diese Immutable sind. Deshalb ist String.Clone sowieso als return this; implementiert. Es ist also gar nicht nötig, die String.Clone-Methode aufzurufen.

  2. Es gibt in deinem Code keine separate Behandlung für Collections (außer für Arrays). Funktioniert das Erstellen einer tiefen Kopie für die gängigen Collections (z.B. List <Person>) trotzdem zuverlässig?

  3. Bei Objekten die ICloneable implementieren, wäre zu überlegen, ob nicht deren Clone-Methode verwendet werden sollte, um die Kopie zu erstellen. Natürlich ist das u.U. keine tiefe Kopie. Aber wenn es eine Clone-Methode gibt, ist deren Semantik vermutlich passender für die Objektart, als die Standard-Semantik von DeepCopy.Copy. Gerade dann, wenn die Objektart unverwaltete Ressourcen benutzt.

  4. Eigentlich fände ich es schöner, wenn keine Kopien der Objekte erstellt werden würden, sondern stattdessen nur der Zustand der Objekte gemerkt werden würde, mit der Option diesen später wieder in die Objekte zurückzuschreiben. Siehe dazu Kopie ohne ICloneable.

herbivore

Floste Themenstarter:in
1.130 Beiträge seit 2007
vor 15 Jahren
  1. Du hast recht,ich hab mich auch schon gewundert, warum Referenceequals true ergibt.
  2. Es werden Arrays und Felder richtig behandelt. Darauf bauen Collections auf. Also ist keinerlei Sonderbehandlung für Collections nötig. (Dictionary funktionierte einwandfrei)
  3. Jup, nur dass das dann keine tiefe Kopie mehr ergibt. Ich könnte vllt. ein eigenes Interface anbieten.
  4. Es wird ja keiner gezwungen und in einigen Fällen ist es praktisch.

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

S
8.746 Beiträge seit 2005
vor 15 Jahren
  1. Es werden Arrays und Felder richtig behandelt. Darauf bauen Collections auf. Also ist keinerlei Sonderbehandlung für Collections nötig. (Dictionary funktionierte einwandfrei)

LinkedList?

Floste Themenstarter:in
1.130 Beiträge seit 2007
vor 15 Jahren

Eine Linkedlist ist ein Objektnetz. Objektnetze werden ebenfalls korrekt kopiert.

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

3.971 Beiträge seit 2006
vor 15 Jahren

Dein ReferenceComparer passt noch nicht ganz. Wenn Equals auf Referenzen arbeitet, nimm für GetHashCode die RuntimeHelpers::GetHashCode-Methode (Object). Die Funktion ruft die Basis-Implementierung von Object.GetHashCode() auf.

Bei Listen, Dictionaries und anderen ähnlichen Klassen wären Überladungen von Copy nicht schlecht.


public List<T> Copy(IEnumerable<T> list) { }
public TList Copy<TList, TVALUE>(IEnumerable<TVALUE> list) where TLIST : IList<TVALUE>, class, new { }
public TDICT Copy<TDICT, TKEY, TVALUE>(IDictionary<TKEY, TVALUE> dict) where TDICT : IDictionary<TKEY, TVALUE>, class, new { }

Dort kann ich als Programmierer direkt angeben, in was für Listen oder HashTables die Kopien landen sollen. Weiterhin erspart das ganze lästiges Casten.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Floste Themenstarter:in
1.130 Beiträge seit 2007
vor 15 Jahren

Dein ReferenceComparer passt noch nicht ganz. Wenn Equals auf Referenzen arbeitet, nimm für GetHashCode die RuntimeHelpers::GetHashCode-Methode (Object). Die Funktion ruft die Basis-Implementierung von Object.GetHashCode() auf.

Für das gleiche Objekt, kommt trotzdem der gleiche Hashcode raus und darauf komt es an. Wenn ein anderes Objekt die gleichen Daten enthält, ebenfalls den gleichen Hashcode hat, kann das Dictionary sie trotzdem unterscheiden, da ich Equals überschreiben habe.
Also: Dein Vorschlag würde höchstens die Performance etwas verbessern.

Dort kann ich als Programmierer direkt angeben, in was für Listen oder HashTables die Kopien landen sollen. Weiterhin erspart das ganze lästiges Casten.

Die Klasse kann aber alles kopieren und nichnur Listen. Also füge ich mal folgendes hinzu:


public static T Copy<T>(T obj)
{
    return (T) Copy(obj);
}

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

W
54 Beiträge seit 2006
vor 14 Jahren

Ersteinmal möchte ich mich für den Code bedanken. Die Klasse ist sehr hilfreich.

Ich habe allerdings jetzt eine Exception bekommen:

"Der Index war außerhalb des Arraybereichs."

Uns zwar hier: array.SetValue(dc.copy(array.GetValue(indices)), indices);

indices ist 1 und array.count ist 0

wurde da irgendwo was nicht abgefangen?

Floste Themenstarter:in
1.130 Beiträge seit 2007
vor 14 Jahren

Ja, es fehlte eine Abfrage, ob das Array leer ist. Ich hab mal eine dazugehackt.

Ersteinmal möchte ich mich für den Code bedanken. Die Klasse ist sehr hilfreich.

Danke. Für was nimmst du sie denn?

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

203 Beiträge seit 2006
vor 14 Jahren

würd ja fast die goto befehle rauslassen:


do
{
    if (elementType.IsPrimitive)
        break;
    if (elementType.IsValueType)
    {
        FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        bool ContainsReferenceTypes = false;
        foreach (FieldInfo f in fields)
        {
            if (!f.FieldType.IsValueType)
            {
                ContainsReferenceTypes = true;
                break;
            }
        }
        if (!ContainsReferenceTypes)
            break;
    }
    {
        //im Array jedes Element auswechseln:
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Ldloc_0);
        gen.Emit(OpCodes.Castclass, typeof(Array));
        gen.EmitCall(OpCodes.Call, iterateArrayInfo, null);
    }
}
while (false);

W
54 Beiträge seit 2006
vor 14 Jahren

Ich benutze den Copyer in zwei Softwares.

In einer wird damit eine eigene Klasse mit Einstellungen kopiert, so ne art "rückgangig" mit einem zurückgehbaren schritt.

In der anderen kann man Abläufe grafisch definieren. Da benutze ich die Klasse um Objekte Clonen zu können.

Wahrscheinlich geht das alles auch anders, wahrscheinlich sogar einfacher. Aber ich bin kein Informatiker und programmiere nur nebenbei. Da helfen solche Klassen schon sehr um schnell zum Ziel zu kommen.