myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Gemeinschaft » .NET-Komponenten und C#-Snippets » Tiefes Kopieren belibiger Objektnetze
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Tiefes Kopieren belibiger Objektnetze

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
Floste
myCSharp.de-Mitglied

avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland


Floste ist offline

Tiefes Kopieren belibiger Objektnetze

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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:

C#-Code:
kopie=DeepCopyer.Copy(original);

C#-Code:
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

Dieser Beitrag wurde 5 mal editiert, zum letzten Mal von Floste am 29.05.2009 12:57.

Neuer Beitrag 14.03.2009 18:24 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.479
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
Neuer Beitrag 14.03.2009 19:39 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland

Themenstarter Thema begonnen von Floste

Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.
Neuer Beitrag 14.03.2009 19:47 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
svenson svenson ist männlich
myCSharp.de-Mitglied

Dabei seit: 15.04.2005
Beiträge: 8.746
Entwicklungsumgebung: Visual Studio .NET 2003
Herkunft: Berlin


svenson ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von floste:
2. Es werden Arrays und Felder richtig behandelt. Darauf bauen Collections auf. Also ist keinerlei Sonderbehandlung für Collections nötig. (Dictionary funktionierte einwandfrei)

LinkedList?
Neuer Beitrag 17.03.2009 23:53 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland

Themenstarter Thema begonnen von Floste

Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Eine Linkedlist ist ein Objektnetz. Objektnetze werden ebenfalls korrekt kopiert.
Neuer Beitrag 18.03.2009 07:57 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
kleines_eichhoernchen kleines_eichhoernchen ist männlich
myCSharp.de-Mitglied

avatar-2079.jpg


Dabei seit: 07.11.2006
Beiträge: 3.971
Entwicklungsumgebung: Visual Studio 2005 (C#)
Herkunft: Ursprünglich Vogtland, jetzt Much


kleines_eichhoernchen ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.

C#-Code:
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.
Neuer Beitrag 18.03.2009 08:06 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland

Themenstarter Thema begonnen von Floste

Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat:
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.

Zitat:
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:

C#-Code:
public static T Copy<T>(T obj)
{
    return (T) Copy(obj);
}
Neuer Beitrag 18.03.2009 08:24 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 2 Monate.
willivonbienemaya
myCSharp.de-Mitglied

Dabei seit: 08.12.2006
Beiträge: 50


willivonbienemaya ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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?

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von willivonbienemaya am 29.05.2009 12:05.

Neuer Beitrag 29.05.2009 12:05 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland

Themenstarter Thema begonnen von Floste

Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

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

Danke. Für was nimmst du sie denn?

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Floste am 29.05.2009 13:02.

Neuer Beitrag 29.05.2009 12:59 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
esskar esskar ist männlich
myCSharp.de-Mitglied

avatar-2885.jpg


Dabei seit: 19.01.2006
Beiträge: 203
Entwicklungsumgebung: VS 2008, .NET >= 3.5
Herkunft: Dubai


esskar ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

würd ja fast die goto befehle rauslassen:

C#-Code:
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);

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von esskar am 29.05.2009 13:36.

Neuer Beitrag 29.05.2009 13:35 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
willivonbienemaya
myCSharp.de-Mitglied

Dabei seit: 08.12.2006
Beiträge: 50


willivonbienemaya ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.
Neuer Beitrag 29.05.2009 14:10 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 11 Jahre.
Der letzte Beitrag ist älter als 11 Jahre.
Antwort erstellen


© Copyright 2003-2020 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 23.09.2020 17:07