Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

Kopie ohne ICloneable [oder warum man Objekte nicht kopieren sollte; Transaktionen auf Objekten]
Quallo
myCSharp.de - Member



Dabei seit:
Beiträge: 994
Herkunft: Nähe Bremen

Themenstarter:

beantworten | zitieren | melden

Muss man denn wirklich so eine Zuordnung haben?

Ich denke, dass man die nur braucht, wenn man eine Gesamtliste führt, welche Objekte in einer Transaction sind, damit man nicht rekursiv immer wieder reinpackt. kann man diese Liste nicht anders führen?

Grüße Christoph
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Quallo,

natürlich braucht man diese Gesamtliste. Man braucht sie bei BusinessObjects (Begründung s.o.). Und man braucht sie hier, wobei es hier ja noch klarer ist, da man ja den Zustand nicht *in* dem Objekt merken kann, sondern das *zu* dem Objekt tun muss. Aber ich wiederhole mich. Das hat noch nicht mal was damit zu tun, ob man sich nur den Zustand der Objekte merken will, oder die Objekte innerhalb von TransActionList klonen würde.

Versuchs mal selbst zu implementieren. Ich denke, dass wirst du merken, wo es hakt.

Ich denke nicht, dass man die Liste anders führen kann, bin aber für konkrete Vorschläge offen.

herbivore
private Nachricht | Beiträge des Benutzers
Programmierhans
myCSharp.de - Experte

Avatar #avatar-1651.gif


Dabei seit:
Beiträge: 4318
Herkunft: Zentralschweiz

beantworten | zitieren | melden

@herbivore

Wenn das BusinessObject von einer BaseClass ableitet (was ja Deine Grundidee war welche Du auch verteidigt hast) dann würde sich doch folgendes anbieten:

die BaseClass erhält ein private Field mit einer Guid.... --> ID diese wird im Default-Constructor des BusinessObject's mit Guid.NewGuid angelegt.

Dann GetHashCode überschreiben, Guid returnen.... das war's

Gruss
Programmierhans

PS: Wieso kompliziert wenn's auch einfach geht.... allerdings halt nur bei einer gemeinsamen BaseClass
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
private Nachricht | Beiträge des Benutzers
ikaros
myCSharp.de - Member



Dabei seit:
Beiträge: 1787

beantworten | zitieren | melden

Hallo,

mal als Erweiterungsvorschlag. Das Kopieren von Eventhandlern wäre sicherlich auch eine interessante Option(wenn auch wahrscheinlich selten gebraucht).
private Nachricht | Beiträge des Benutzers
Quallo
myCSharp.de - Member



Dabei seit:
Beiträge: 994
Herkunft: Nähe Bremen

Themenstarter:

beantworten | zitieren | melden

Wenn es wirklich nur um die Zuordnung geht, dann reicht auch ein object[x,2] wo man ein object dem anderen zuordnet.
Das Problem ist da eher die Performance.
Und da man sowohl bei StartTransaction, als auch bei Commit und bei Rollback nur seriellen zugriff auf die wie auch immer gearteten Listen hat(man iteriert nur durch), sehe ich das mit einem Dictionary nicht unbedingt performanter.

Das Problem ist nur die Liste zu führen, welche Objekte man schon in einer Transaction drin hat, damit man keine endlos rekursionen hat. Da geht DictionaryInstanz.ContainsKey(irgendwas) natürlich wesentlich schneller als eine andere IList.Contains-Implementierung.
Ich verstehe deswegen auch, warum du gerne das Dictionary benutzen möchtest und die Adresse des Objects zum Hashen nehmen willst.
Das ist mir klar. Die Frage ist, ob es nicht eine andere Möglichkeit gibt. Wie sieht es mit einer BinarySearch aus?

Grüße, der wahrscheinlich deine Geduld sehr strapazierende, Christoph
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Programmierhans,

dein Vorschlag ist in der Tat bestechend einfach. Und es ist sicher sinnvoll, ihn umzusetzen, weil man dann Business-Objekte auch in anderen Fällen als Key in Hashtables/Dictionaries benutzen kann. Allerdings gibt es ja durch das Zusammenspiel unser beiden Vorschläge schon ein Proof-of-Concept für die Transaktionierung von Business-Objekten. Für diese stellt sich das Problem m.E. also gar nicht mehr, wogegen die TransactionList ja gerade mit beliebigen Objekten arbeiten können soll und auf deren GetHashCode hat man eben leider keine Einfluss. Man müsste schon Object die Guid und GetHashCode "unterjuben", damit es für TransactionList was bingt.

Hallo ikaros,

ich glaube EventHandler-Objekte müsste man gar nicht gesondert behandeln. Der Vorschlag soll ja ohnehin mit beliebigen Objekten klarkommen. Da wären die EventHandler einfach mit drin.

Hallo Quallo,

fein, dass du meinem Vorschlag gefolgt bist. Du hast jetzt genau die springenden Punkte herausgearbeitet. Ich stimme mit deiner Einschätzung vollkommen überein. Die sich aus dem Zusammenspiel von "Dictionary geht nicht" und "man muss Endlosrekursion vermeiden" ergebenden Performance-Problem (quadratisch statt linear), ist genau die Klippe.

Für BinarySearch braucht man eine Ordnung auf den Objekten. Auch bei allen anderen "beschleunigenden" Datenstukturen, wie B-Bäume o.ä. Und diese Ordnung kann man m.E. ohne Zugriff auf die Identität "als Zahl" nicht definieren/verwenden.

herbivore
private Nachricht | Beiträge des Benutzers
Quallo
myCSharp.de - Member



Dabei seit:
Beiträge: 994
Herkunft: Nähe Bremen

Themenstarter:

beantworten | zitieren | melden

Habt ihr euch schon mal System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(object) angeschaut?

Ich weiß nicht direkt ob das weiterhilft, aber es wurde in vielen Newsgroups als Ersatz für Javas IdentityHashcode genannt.
Die Funktion findet sich glaube ich unter java.lang.identityhashcode wieder...

Vielleicht muss man auch einen Ausflug in die Managed C++ Welt machen. Dort kann man doch mit Pointern arbeiten und dadurch eine Adresse kriegen, oder nicht? Wäre es nicht möglich in MC++ eine Klasse abgeleitet von Hashtable zu machen, die Add, AddRange und ContainsKey überschreibt und die Adresse des übergebenen Objekts nimmt.
Praktisch so:


public class adresshashtable : hashtable
{
  public override Add(object key, object value)
  {
     base.Add(getadressofobject(key),value));
  }
    
...


  private int getadressofobject(object adressobject)
  {
     int adress = 0;
    ...
    return adress
  }
}
Wäre das eine Möglichkeit?

Mit partial Classes könnte man auch nur die getadressofobject(object adressobject) in MC++ implementieren, wenn ich das richtig sehe!

Eine weitere Möglichkeit wäre es doch mit Listen in Listen zu arbeiten.
Dass man zum Beispiel pro Type eine Liste macht, z.B. eine Hashtable in der Type zu List<Type>) zugeordnet ist. Dann sucht man zu dem aktuellen Type die Liste und sucht da über Contains das entsprechende Objekt. Das ist auf jeden Fall schon mal schneller(Ich glaube ähnlich arbeitet auch Hashtable intern(mehrere Container ineinander)).
Vielleicht gibt es außer Type und der Adresse noch andere Kriterien eines Objektes, die sich nicht ändern, aus denen man vielleicht einen Hashcode erstellen kann oder es zumindest für eine Teilliste nehmen kann.



Grüße Christoph
private Nachricht | Beiträge des Benutzers
Quallo
myCSharp.de - Member



Dabei seit:
Beiträge: 994
Herkunft: Nähe Bremen

Themenstarter:

beantworten | zitieren | melden

Ich habe ein Beispiel in Mc++ gemacht:

namespace Adresseint
{
	__gc public class AdressTest
	{
		public :

			static int GiveAdress(System::thumbsup:bject __gc* AdressObject)
			{
				return (int)(&AdressObject);
			};


	};
}

Er gibt mir immer brav die Adresse zurück, allerdings die Adresse von dem lokalen AdressObject auf dem Stack.

Wie muss ich das verändern, um die Adresse zu bekommen, auf die AddressObject zeigt?
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Quallo,

System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(object) klingt vielversprechend. Ob es wirklich hilft, ist nicht klar. Es ist wie folgt implementiert:


public static int GetHashCode(object o)
{
  return object.InternalGetHashCode(o);
}
und damit quasi genauso wie Object.GetHashCode ():


public virtual int GetHashCode()
{
   return object.InternalGetHashCode(this);
}

Was man gegenüber Object.GetHashCode () gewonnen hat, ist dass man den Hashcode unabhängig vom Objekttyp immer über object.InternalGetHashCode berechnet bekommt. Hefen tut das aber nur dann, wenn object.InternalGetHashCode nur und genau die Idendentät des Objekts in einen HashCode wuselt. Ob das so ist, weiß ich aber leider nicht.

Zu Managed C++ kann ich nicht viel sagen. Ich würde aber denken, dass man in *Managed* C++ nicht mit Pointern arbeiten kann/darf, höchsten wieder mit unsafe wie in C# auch. Ist aber nur eine Vermutung.

Immerhin hat mich sehr aufgebaut, dass es in Java IdentityHashcode gibt. Das zeigt mir zumindestens mal, dass ich nicht spinne, sondern dass ich wirklich was ganz normales will.

Die Aufteilung der Listen nach Typ könnte praktisch in vielen Fällen was bringen, ändert aber nichts am eigentlichen Problem. Wenn man z.B. einen großen TreeNode-Baum transaktionieren will, sind ja alle Objekte vom gleichen Typ.

Deine anderen Fragen kann ich nicht beantworten. Aber vielleicht weiß ja jemand anders noch was.

herbivore
private Nachricht | Beiträge des Benutzers
Quallo
myCSharp.de - Member



Dabei seit:
Beiträge: 994
Herkunft: Nähe Bremen

Themenstarter:

beantworten | zitieren | melden

Es gibt eine Klasse names ObjectID:

TestCode:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;

namespace ObjectID
{
    class Program
    {

        DateTime dt1 = new DateTime(2005, 12, 24, 20, 0, 0);
        DateTime dt2 = new DateTime(2005, 12, 24, 20, 0, 0);
        string s1 = "Stein";
        string s2 = "Stein";
        string s3 = "Haus";


        static void Main(string[] args)
        {
            Program currProg = new Program();
            currProg.test();
        }

        public void test()
        {

            ObjectIDGenerator oidgen = new ObjectIDGenerator();

            bool b;

            Console.WriteLine(dt1);
            Console.WriteLine(oidgen.GetId(dt1, out b));
            Console.WriteLine(b);

            Console.WriteLine();
            Console.WriteLine(dt2);
            Console.WriteLine(oidgen.GetId(dt2, out b));
            Console.WriteLine(b);

            Console.WriteLine();
            Console.WriteLine(dt1);
            Console.WriteLine(oidgen.GetId(dt1, out b));
            Console.WriteLine(b);


            Console.WriteLine();
            Console.WriteLine(s1);
            Console.WriteLine(oidgen.GetId(s1, out b));
            Console.WriteLine(b);

            Console.WriteLine();
            Console.WriteLine(s2);
            Console.WriteLine(oidgen.GetId(s2, out b));
            Console.WriteLine(b);

            Console.WriteLine();
            Console.WriteLine(s1);
            Console.WriteLine(oidgen.GetId(s1, out b));
            Console.WriteLine(b);

            
            s1 = "Haus";

            Console.WriteLine();
            Console.WriteLine(s1);
            Console.WriteLine(oidgen.GetId(s1, out b));
            Console.WriteLine(b);

            Console.WriteLine();
            Console.WriteLine(s3);
            Console.WriteLine(oidgen.GetId(s3, out b));
            Console.WriteLine(b);




        }
    }
}



Ausgabe:

24.12.2005 20:00:00
1
True

24.12.2005 20:00:00
2
True

24.12.2005 20:00:00
3
True

Stein
4
True

Stein
4
False

Stein
4
False

Haus
5
True

Haus
5
False


Sieht also nicht sehr vielversprechend aus.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Quallo,

wieso nicht? Das ist doch genau, was wir suchen! Auch die Ausgabe sieht doch gut aus.

herbivore

PS: Allerdings ist nicht klar, ob GetId auch wirklich den gewünschten Aufwand von O(1) hat.
private Nachricht | Beiträge des Benutzers
Quallo
myCSharp.de - Member



Dabei seit:
Beiträge: 994
Herkunft: Nähe Bremen

Themenstarter:

beantworten | zitieren | melden

Ich habe noch ein paar Anmerkungen:


24.12.2005 20:00:00
1
True

24.12.2005 20:00:00
2
True

24.12.2005 20:00:00
3 --> Wieso drei? Das ist doch s1, also müsste so wie wir es wollen 1, false rauskommen. Aber das ist ja ein WerteTyp, der wird geboxt, da kann das gut sein, dass das nicht klappt
True

Stein
4
True

Stein
4 --> Der String ist gleich, hat aber nicht die gleiche Adresse wie s1, also dürfte kein true rauskommen.
False

Stein
4
False

Haus
5
True

Haus
5 --> siehe dem Kommentar davor
False

Das Testen mit selbst erstellen Klassen wäre noch nett!
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Quallo,

3: DateTime ist ein Value-Type. Der wird bei der Parameterübergabe geboxt und GetId bekommt daher jedesmal eine neue Kopie übergeben.

4: Der Compiler legt für "Stein" nur ein String-Exemplar an, egal wieoft du "Stein" in den Code schreibst. Es wird also dreimal dasselbe Objekt übergeben.

5: siehe die Antwort davor:-)

Sieht also alles gut aus.

herbivore
private Nachricht | Beiträge des Benutzers
Quallo
myCSharp.de - Member



Dabei seit:
Beiträge: 994
Herkunft: Nähe Bremen

Themenstarter:

beantworten | zitieren | melden

Wunderbar!
Dann können du/ich/wir oder ich/du/wir oder wir/ich/du oder wer anders sich in den nächsten tagen ja mal an die Implementierung machen.
Die Frage ist ob ObjectID auch funktioniert, wenn man Felder eines Objektes verändert.

Grüße Christoph
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Quallo,

ich werde in den nächsten Tagen sicher nicht dazu kommen. Aber du hast ja jetzt selbst alle Voraussetzungen. Und eine gute Übung ist es alle mal.

Hier noch spaßeshalber der Code von GetId. Hab ihn mir noch nicht genau angeguckt, aber er basiert wohl tatsächlich auf RuntimeHelpers.GetHashCode:


public virtual long GetId(object obj, out bool firstTime)
{
      bool flag1;
      long num1;
      if (obj == null)
      {
            throw new ArgumentNullException("obj", Environment.GetResourceString("ArgumentNull_Obj"));
      }
      int num2 = this.FindElement(obj, out flag1);
      if (!flag1)
      {
            this.m_objs[num2] = obj;
            this.m_ids[num2] = this.m_currentCount++;
            num1 = this.m_ids[num2];
            if (this.m_currentCount > ((this.m_currentSize * 4) / 2))
            {
                  this.Rehash();
            }
      }
      else
      {
            num1 = this.m_ids[num2];
      }
      firstTime = !flag1;
      return num1;
}
 
private int FindElement(object obj, out bool found)
{
      int num1 = RuntimeHelpers.GetHashCode(obj);
      int num2 = 1 + ((num1 & 0x7fffffff) % (this.m_currentSize - 2));
      while (true)
      {
            int num3 = ((num1 & 0x7fffffff) % this.m_currentSize) * 4;
            for (int num4 = num3; num4 < (num3 + 4); num4++)
            {
                  if (this.m_objs[num4] == null)
                  {
                        found = false;
                        return num4;
                  }
                  if (this.m_objs[num4] == obj)
                  {
                        found = true;
                        return num4;
                  }
            }
            num1 += num2;
      }
}

private void Rehash()
{
      int num1 = 0;
      int num3 = this.m_currentSize;
      while ((num1 < ObjectIDGenerator.sizes.Length) && (ObjectIDGenerator.sizes[num1] ≤ num3))
      {
            num1++;
      }
      if (num1 == ObjectIDGenerator.sizes.Length)
      {
            throw new SerializationException(Environment.GetResourceString("Serialization_TooManyElements"));
      }
      this.m_currentSize = ObjectIDGenerator.sizes[num1];
      long[] numArray1 = new long[this.m_currentSize * 4];
      object[] objArray1 = new object[this.m_currentSize * 4];
      long[] numArray2 = this.m_ids;
      object[] objArray2 = this.m_objs;
      this.m_ids = numArray1;
      this.m_objs = objArray1;
      for (int num4 = 0; num4 < objArray2.Length; num4++)
      {
            if (objArray2[num4] != null)
            {
                  bool flag1;
                  int num2 = this.FindElement(objArray2[num4], out flag1);
                  this.m_objs[num2] = objArray2[num4];
                  this.m_ids[num2] = numArray2[num4];
            }
      }
}
Zitat
Die Frage ist ob ObjectID auch funktioniert, wenn man Felder eines Objektes verändert.
Kannst du ja mal testen. Aber mittlerweile denke ich, dass RuntimeHelpers.GetHashCode dem IdentityHashcode von Java entspricht.

herbivore
private Nachricht | Beiträge des Benutzers
dN!3L
myCSharp.de - Experte

Avatar #avatar-2985.png


Dabei seit:
Beiträge: 3138

beantworten | zitieren | melden

Öhm, gibt's eigentlich schon Ergebnisse?
Bin nämlich gerade dabei, mir ein tolles Beispiel für mein AOP-Seminar einfallen zu lassen. Transaktionalität für Objekte gefiele mir da sehr gut. Mit AOP lässt sich da ja sicher noch einiges rausholen...

Gruß
dN!3L
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo dN!3L,

ich weiß nicht, ob Quallo noch was gemacht hat. Was mich anbelangt, sind alle Ergebnisse hier in diesem Thread veröffentlicht.

Wenn du einen Seimiarvortrag halten willst (was mich freuen würde), wäre es ja m.E. ohnehin nicht legitim, das Endergebnis einfach zu übernehmen. Anderseits bieten die bisherigen Beiträge aber doch eine sehr solide Grundlage für einen eigenen Transactionmanager. Du könntest ihn dann hier posten.

herbivore
private Nachricht | Beiträge des Benutzers
dN!3L
myCSharp.de - Experte

Avatar #avatar-2985.png


Dabei seit:
Beiträge: 3138

beantworten | zitieren | melden

Zitat
Original von herbivore
Wenn du einen Seimiarvortrag halten willst (was mich freuen würde), wäre es ja m.E. ohnehin nicht legitim, das Endergebnis einfach zu übernehmen. Anderseits bieten die bisherigen Beiträge aber doch eine sehr solide Grundlage für einen eigenen Transactionmanager. Du könntest ihn dann hier posten.
Das ganze hier könnte ich dann eh nicht übernehmen (da es ja um AOP geht). Hier ginge es mir eher um die Idee, wie man den Zustand eines Objektes speichern kann. Aber mal sehen, wie weit ich mit AOP komme, das zu verwendende Framework hat noch einige Einschränkungen, die nicht ganz so transparent zulässt, wie ich es mir erhofft habe. Falls es was wird, kann ich die Ergebnisse dann natürlich posten.

Gruß
dN!3L
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo dN!3L,
Zitat
Hier ginge es mir eher um die Idee, wie man den Zustand eines Objektes speichern kann.
hm, das ist doch grade Thema des Threads und es wird ja nicht nur genau diese Idee beschrieben, sondern sogar durch Code demonstriert.

herbivore
private Nachricht | Beiträge des Benutzers
Quallo
myCSharp.de - Member



Dabei seit:
Beiträge: 994
Herkunft: Nähe Bremen

Themenstarter:

beantworten | zitieren | melden

Leider hat es der Unternehmensalltag nicht zugelassen was zu entwickeln. Aber die Grundlagen sollten wir hier haben!


Grüße, Christoph
private Nachricht | Beiträge des Benutzers
dr4g0n76
myCSharp.de - Experte

Avatar #avatar-1768.jpg


Dabei seit:
Beiträge: 3047
Herkunft: Deutschland

Serialisierung

beantworten | zitieren | melden

Wenn die entsprechenden Fields usw. serialisierbar sind, müsste doch auch die Serialisierung ein Clonde ermöglichen:


 public Program Copy()
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        MemoryStream memoryStream = new MemoryStream();
        binaryFormatter.Serialize(memoryStream, this);
        memoryStream.Position = 0;
        return (Program)binaryFormatter.Deserialize(memoryStream);
    }
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
private Nachricht | Beiträge des Benutzers
dN!3L
myCSharp.de - Experte

Avatar #avatar-2985.png


Dabei seit:
Beiträge: 3138

beantworten | zitieren | melden

Sodenn,

ich sag dann mal, dass ich eine Lösung habe...
Ich habe einen Transaktionsmanager, bei dem ich mir Transaktionen holen kann (Schachtelungen möglich) und der den Zustand von Objekten dann speichert und bei Bedarf wiederherstellt. Da ich das im Rahmen eines Seminars für aspektorientierte Programmierung mache, brauch ich die Objekte nicht selbst melden, sondern dass macht ein Aspekt für mich, der alle während der Transaktion angefassten Objekte speichert.

Problem: Wenn ich den ganzen Code für die Ausarbeiten benutzen will, kann ich den hier jetzt schon zeigen???


Gruß
dN!3L


P.S.: Von "hier" hab ich das mit den Feldern kopieren und speichern... Das meinte ich mit "nur die Idee" weiter oben
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo dN!3L,

freut mich erstmal, dass du das Thema tatsächlich gewählt und bearbeitet hast. Ich bin an dem Code interessiert. Gerade wenn du ihn hier postest, ist ja deine Urheberschaft nachgewiesen. Aber ich habe auch kein Problem damit, wenn du mit der Veröffentlichung bis nach dem Vortrag wartest.

herbivore
private Nachricht | Beiträge des Benutzers
dN!3L
myCSharp.de - Experte

Avatar #avatar-2985.png


Dabei seit:
Beiträge: 3138

beantworten | zitieren | melden

Also ich werd den Code dann erstmal die nächste Woche etwas quälen und eventuelle Verbesserungen einfügen und dann mal weiter sehen...

Gruß
dN!3L

P.S.: @herbivore: Wo hast du eigentlich immer solch konkrete Implementierungsdetails von Framework-Zeugs her?
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo dN!3L,

wenn ich deine Frage richtig verstehe ist die Antwort: Lutz Roeder's .NET Reflector.

herbivore
private Nachricht | Beiträge des Benutzers
dN!3L
myCSharp.de - Experte

Avatar #avatar-2985.png


Dabei seit:
Beiträge: 3138

beantworten | zitieren | melden

Zitat
Original von herbivore
Ich bin an dem Code interessiert. Gerade wenn du ihn hier postest, ist ja deine Urheberschaft nachgewiesen.

So, da bin ich wieder. Meine Codebespiele im Seminar sind zwar etwas anders ausgefallen, als ich gedacht hatte, aber das hat dem Transaktionsmanager nicht geschadet (er ist nur etwas nebensächlicher als angedacht geworden). Und ich wollte ihn ja posten.

Also hier der Code:

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



namespace OrderSystemDemo
{
	/// <summary>
	/// Verwaltet die Transaktionen
	/// </summary>
	public static class TransactionManager
	{
		// Auflistung mit den gerade aktiven Transaktionen und der Objekte, die in diesen Transaktionen potentiell geändert wurden
		private static List<KeyValuePair<Transaction,Dictionary<int,ObjectState>>> transactionsVsUsedObjects = new List<KeyValuePair<Transaction,Dictionary<int,ObjectState>>>();



		/// <summary>
		/// überprüft, ob der Zustand des gemeldeten Objekts in einer Transaktion gespeichert werden muss und tut dies bei Bedarf
		/// </summary>
		/// <param name="theObject">das Objekt, dessen Zustand eventuell gespeichert werden muss</param>
		public static void RegisterObject(object theObject)
		{
			// nur wenn es aktive Transaktionen gibt, muss was getan werden
			if (transactionsVsUsedObjects.Count!=0)
			{
				// da geschachtelte Transaktionen, muss das Objekt nur der in der Verschachtelung tiefsten Transaktion zugeordnet werden
				Dictionary<int,ObjectState> topTransaktionUsedObjectList = transactionsVsUsedObjects[transactionsVsUsedObjects.Count-1].Value;

				// wenn das gemeldete Objekt noch nicht für diese Transaktion gespeichert wurde, dies jetzt tun
				if (!topTransaktionUsedObjectList.ContainsKey(theObject.GetHashCode()))
				{
					// eine neue Liste für die Felddaten anlegen
					List<ObjectState.FieldData> fieldsList = new List<ObjectState.FieldData>();

					// alle Felder des Objekts und dessen Basistypen speichern
					for (Type type=theObject.GetType();type!=null;type=type.BaseType)
					{
						// jedes Feld des aktuellen Typs und dessen Wert speichern
						FieldInfo[] fieldInfos = type.GetFields(BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Public);
						foreach (FieldInfo fieldInfo in fieldInfos)
							fieldsList.Add(new ObjectState.FieldData(fieldInfo,fieldInfo.GetValue(theObject)));
					}

					// den gespeicherten Zustand mit der Objektreferent für die aktuelle Transaktion speichern
					topTransaktionUsedObjectList.Add(theObject.GetHashCode(),new ObjectState(theObject,fieldsList));
				}
			}
		}



		/// <summary>
		/// wenn eine Transaktion gestartet wird, diese in der Transaktionenliste initialisieren
		/// </summary>
		private static void transaction_TransactionStart(object sender,EventArgs e)
		{
			transactionsVsUsedObjects.Add(new KeyValuePair<Transaction,Dictionary<int,ObjectState>>(sender as Transaction,new Dictionary<int,ObjectState>()));
		}



		/// <summary>
		/// wenn eine Transaktion committed wird, alle in ihr geschachtelten Transaktionen ebenfalls committen und aus der Transaktionenliste entfernen
		/// </summary>
		private static void transaction_TransactionCommit(object sender,EventArgs e)
		{
			// alle geschachtelten Transaktionen auch committen (mit der "obersten"/am tiefsten geschachtelten Transaktion anfangen und dann bis zur aktuellen durcharbeiten)
			Transaction topTransaktion = transactionsVsUsedObjects[transactionsVsUsedObjects.Count-1].Key;
			while (topTransaktion!=(sender as Transaction))
			{
				topTransaktion.Commit();
				topTransaktion = transactionsVsUsedObjects[transactionsVsUsedObjects.Count-1].Key;
			}

			// Commit = die Transaktion aus der Transaktionenliste löschen
			transactionsVsUsedObjects.RemoveAt(transactionsVsUsedObjects.Count-1);
		}



		/// <summary>
		/// wenn ein Rollback einer Transaktion durchgeführt werden soll, alle in ihr geschachtelten Transaktionen ebenfalls rollbacken
		/// </summary>
		private static void transaction_TransactionRollback(object sender,EventArgs e)
		{
			// alle geschachtelten Transaktionen auch rollbacken (mit der "obersten"/am tiefsten geschachtelten Transaktion anfangen und dann bis zur aktuellen durcharbeiten)
			Transaction topTransaktion = transactionsVsUsedObjects[transactionsVsUsedObjects.Count-1].Key;
			while (topTransaktion!=(sender as Transaction))
			{
				topTransaktion.Rollback();
				topTransaktion = transactionsVsUsedObjects[transactionsVsUsedObjects.Count-1].Key;
			}

			// Rollback = Objektzustand wiederherstellen...
			foreach (ObjectState objectState in transactionsVsUsedObjects[transactionsVsUsedObjects.Count-1].Value.Values)	// alle gespeicherten Objekte...
				foreach (ObjectState.FieldData fieldData in objectState.FieldDataList)										// ... davon alle Felder ...
					fieldData.FieldInfo.SetValue(objectState.ObjectRef,fieldData.FieldValue);								// ... wiederherstellen

			// ... und die Transaktion aus der Transaktionsliste löschen
			transactionsVsUsedObjects.RemoveAt(transactionsVsUsedObjects.Count-1);
		}





		/// <summary>
		/// Interface für eine Transaktion
		/// </summary>
		public interface ITransaction
		{
			/// <summary>
			/// die Transaktion starten
			/// </summary>
			void Start();

			/// <summary>
			/// die Transaktion erfolgreich beenden
			/// </summary>
			void Commit();

			/// <summary>
			/// den Zustand vor Start der Transaktion wiederherstellen
			/// </summary>
			void Rollback();
		}



		/// <summary>
		/// erstellt eine neue Transaktion
		/// </summary>
		/// <returns>eine neue Transaktion</returns>
		public static ITransaction CreateTransaction()
		{
			// neue Transaktion erstellen
			Transaction transaction = new Transaction();

			// Eventhandler beim Transaktionmanager registrieren
			transaction.TransactionStart += new EventHandler(transaction_TransactionStart);
			transaction.TransactionRollback += new EventHandler(transaction_TransactionRollback);
			transaction.TransactionCommit += new EventHandler(transaction_TransactionCommit);

			// die gerade erstellte Transaktion zurückgeben
			return transaction;
		}



		/// <summary>
		/// Ausnahme für Transaktionen
		/// </summary>
		public class TransactionException : Exception
		{
			/// <summary>
			/// erstellt eine neue Transaktionenausnahme
			/// </summary>
			/// <param name="message">die Fehlermeldung</param>
			public TransactionException(string message)
				: base(message)
			{
			}
		}



		/// <summary>
		/// eine Transaktion, die die Transaktionsvorgänge melden kann
		/// </summary>
		private class Transaction : ITransaction
		{
			/// <summary>
			/// die Transaktion soll gestartet werden
			/// </summary>
			public event EventHandler TransactionStart;

			/// <summary>
			/// die Transaktion soll erfolgreich beendet werden
			/// </summary>
			public event EventHandler TransactionCommit;

			/// <summary>
			/// der Zustand vor Start der Transaktion soll wiederhergestellt werden
			/// </summary>
			public event EventHandler TransactionRollback;


			private bool isActive = false;	// wenn true, ist die Transaktion gerade aktiv


			/// <summary>
			/// Transaktion starten
			/// </summary>
			public void Start()
			{
				if (isActive)
					throw new TransactionException("Die Transaktion wurde bereits gestarted.");

				if (TransactionStart!=null)
					TransactionStart(this,null);
				isActive = true;
			}


			/// <summary>
			/// Transaktion erfolgreich beenden
			/// </summary>
			public void Commit()
			{
				if (!isActive)
					throw new TransactionException("Die Transaktion wurde nicht gestartet oder ist schon beendet.");

				if (TransactionCommit!=null)
					TransactionCommit(this,null);
				isActive = false;
			}


			/// <summary>
			/// Zustand vor Start der Transaktion wiederherstellen
			/// </summary>
			public void Rollback()
			{
				if (!isActive)
					throw new TransactionException("Die Transaktion wurde nicht gestartet oder ist schon beendet.");

				if (TransactionRollback!=null)
					TransactionRollback(this,null);
				isActive = false;
			}
		}



		/// <summary>
		/// enthält Daten zum Zustand eines Objektes zu einem Zeitpunkt und das Objekt selbst
		/// </summary>
		private class ObjectState
		{
			/// <summary>
			/// Konstruktor
			/// </summary>
			/// <param name="objectRef">Referenz auf das Objekt</param>
			/// <param name="fieldDataList">Liste mit den Daten der Felder des Objekts</param>
			public ObjectState(object objectRef,List<FieldData> fieldDataList)
			{
				this.ObjectRef = objectRef;
				this.FieldDataList = fieldDataList;
			}

			/// <summary>
			/// Referenz auf das Objekt
			/// </summary>
			public object ObjectRef;

			/// <summary>
			/// Liste mit den Daten der Felder des Objekts
			/// </summary>
			public List<FieldData> FieldDataList = new List<FieldData>();


			/// <summary>
			/// enthält die Daten eines Feldes
			/// </summary>
			public class FieldData
			{
				/// <summary>
				/// Konstruktor
				/// </summary>
				/// <param name="fieldInfo">das FieldInfo des Feldes</param>
				/// <param name="fieldValue">der Wert des Feldes</param>
				public FieldData(FieldInfo fieldInfo,object fieldValue)
				{
					this.FieldInfo = fieldInfo;
					this.FieldValue = fieldValue;
				}

				/// <summary>
				/// das FieldInfo des Feldes
				/// </summary>
				public FieldInfo FieldInfo;

				/// <summary>
				/// der Wert des Feldes
				/// </summary>
				public object FieldValue;
			}
		}
	}
}


Erstmal die Benutzung von Transaktionen:

TransactionManager.ITransaction transaction = TransactionManager.CreateTransaction();

try
{
	transaction.Start();

	// die bearbeiteten Objekte beim Transaktionmanager melden
	// irgendwas mit den Objekten machen

	transaction.Commit();
}
catch
{
	Console.WriteLine("Rolling back transactions...");
	transaction.Rollback();
}


Aufgrund der Tatsache, dass es um aspektorientierte Programmierung ging, wird nur eine flache Kopie erstellt. Denn in der AOP-Realisierung hab ich einen Aspekt, der die in der Zwischenzeit angefassten Objekte alle selbst/automatisch dem Transaktionmanager meldet. Es werden dann also alle nach TA.Start() angefassten (Methodenaufruf, Getter/Setter) Objekte (und nur die) bis zum Abschluss gemeldet.

Jedenfalls muss man bei Nichtbenutzung von AOP alle Objekte, die nach der Transaktion evtl. wiederhergestellt werden sollen, innerhalb dieser Transaktion dem TA-Mangager melden:

// dem Transaktionmanager das Objekt melden, dessen Zuständ sich potentiell ändern wird
TransactionManager.RegisterObject(Context.Instance);

Wär schön, wenn jemand mal das Ergebnis verifiziert.

Gruß
dN!3L


P.S.: Wo ich gerade bei AOP bin: Ich hätte durch meine Ausarbeitung ja auch einen schönen Beitrag für die Artikelseite anzubieten... (nur kann ich da ja nichts schreiben)
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo dN!3L,

hm, verifizieren? Wäre eine Menge Arbeit, wenn man das ernsthaft machen wollte. Aber da mich - wie ich ja auch schon vorher schrieb - der Code interessiert, habe ich ihm mir natürlich angeguckt. Um die eigentlichen Probleme hast du dich ja elegant drum rum gemogelt. :-)

Vermutlich sind alle Objekt, die du transaktionierst von eigenen Klassen und da eine flache Kopie zu erstellen, ist natürlich unkritisch. Haarig wird es, wenn man sowas transaktionieren will, wie z.B. einen FileStream.

Außerdem verwendest du als Key für die zu transaktionierenden Objekte einfach deren GetHashCode und ignorierst das weiter oben diskutierte Problem, dass man nicht voraussetzen kann, dass das für alle Objekte angemessen implementiert ist.

Wenn ich es mir recht überlegt, habe ich wohl die Weiterarbeit an dem Transaktionsmanager genau aus diesen beiden Gründen eingestellt.

Bei meinem BusinessObject-Ansatz von oben löse ich diese Probleme zwar auch nicht, aber es ist durch das Design ausgeschlossen, dass die auftreten.

Deshalb bin ich jetzt der Meinung, dass es zwar schön wäre, einen Transaktionsmanager zu haben, man sich aber bei dem momentanen Stand der Softwaretechnik wohl eher mit der BusinessObject-Lösung zufrieden geben muss. Diese hat zwar ihre Beschränkungen, aber im nachhinein betrachtet, hat sie die Beschränkungen genau da, wo keine Lösung möglich ist.

Als ich mir die BusinessObject-Lösung gerade noch mal angeguckt habe, ist mir aufgefallen, dass man sie noch verbessern könnte, wenn man in _StartTransaction statt des direkten Sichern des Zustands eine virtuelle Methode SaveState aufruft, die das Sichern des Zustands übernimmt, damit man in Unterklassen dieses Verhalten beeinflussen kann. Dann hätte ähnlich wie bei der Serialisierung die Möglichkeit, es einfach zu haben, in dem man die Standardimplementierung nutzt, ohne eine die benutzerdefinierte Vorgehensweise auszuschließen.

herbivore

PS: Wegen der Vorgehensweise beim Veröffentlichen eines Artikels schicke ich dir später noch eine PM.
private Nachricht | Beiträge des Benutzers
dN!3L
myCSharp.de - Experte

Avatar #avatar-2985.png


Dabei seit:
Beiträge: 3138

beantworten | zitieren | melden

Zitat
Original von herbivore
hm, verifizieren? Wäre eine Menge Arbeit, wenn man das ernsthaft machen wollte.
Naja, rübergucken, ob nicht was gravierendes falsch ist.
Zitat
Um die eigentlichen Probleme hast du dich ja elegant drum rum gemogelt. :-)
Es ging bei mir ja auch um die Anwendung in AOP. Und da ergeben sich eben einige Probleme nicht mehr.
Zitat
Vermutlich sind alle Objekt, die du transaktionierst von eigenen Klassen und da eine flache Kopie zu erstellen, ist natürlich unkritisch. Haarig wird es, wenn man sowas transaktionieren will, wie z.B. einen FileStream.
Erstens kann man sich ja aussuchen, welche Objekte in die TA kommen sollen ;-) Aber wie will mann denn den Zustand eines FileStreams speichern? Es werden alle Felder (also im Prinzip die ganze Speicherbelegung durch ein Objekt) gespeichert und wieder hergestellt. Und einen Zustand kann sich ein Objekt nur durch Speicher merken. Wenn man sich jetzt z.B. Objekte mit Dateisystemoperationen anguckt, haben die eben externen Speicher, den man mit den "normalen" Mitteln nicht bearbeiten kann.
Zitat
Außerdem verwendest du als Key für die zu transaktionierenden Objekte einfach deren GetHashCode und ignorierst das weiter oben diskutierte Problem, dass man nicht voraussetzen kann, dass das für alle Objekte angemessen implementiert ist.
Hm, also ich hab oben rausgelesen, dass der Hashcode reicht...

Zitat
Deshalb bin ich jetzt der Meinung, dass es zwar schön wäre, einen Transaktionsmanager zu haben, man sich aber bei dem momentanen Stand der Softwaretechnik wohl eher mit der BusinessObject-Lösung zufrieden geben muss. Diese hat zwar ihre Beschränkungen, aber im nachhinein betrachtet, hat sie die Beschränkungen genau da, wo keine Lösung möglich ist.
Oder man benutzt AOP und kann alle angefassten Objekte (meinetwegen auch mit etwas Aufwand Filesystemoperationen) registrieren. Aber um komplett alles Umzusetzen reicht auch der derzeitige Stand der AOP-Technik (in .NET) nicht aus.


Gruß
dN!3L
Zitat
PS: Wegen der Vorgehensweise beim Veröffentlichen eines Artikels schicke ich dir später noch eine PM.
Sicher Mitte Februar dann...
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo dN!3L,

also ich habe nichts gesehen, was gravierend falsch wäre. Und an welchen Stellen ich Probleme sehen, habe ich ja geschrieben. Das mit dem "rummogelt" war keine Kritik, zumal du dich ja nur um die Stellen "rumgemogelt" hast, an denen ich es auch getan habe.

Objekte mit ummanged Daten/Zustand (also auch FileStream) lassen sich eben aus den von dir genannten Gründen nicht so einfach speichern. Ich sehe das Problem deshalb darin, dass man sie dem Transaktionsmanger übergeben kann und der so tut, als könne er.

Tja, das mit dem Hashcode halte ich für offen. Zumindest hast du das Problem, dass dein TA durcheinanderkommt, wenn eine GetHashCode-Methode falsch implementiert ist. Nun kann man sagen, dass man immer Probleme hat, wenn eine Methode falsch implementiert ist. Das wäre durchaus ein Argument. Aber es geht eben besser: Oben ging es ja eher in Richtung RuntimeHelpers.GetHashCode. Damit sollte es dann immer klappen, wenn unsere oben angestellten Vermutungen richtig sind.

herbivore
private Nachricht | Beiträge des Benutzers
dN!3L
myCSharp.de - Experte

Avatar #avatar-2985.png


Dabei seit:
Beiträge: 3138

beantworten | zitieren | melden

Zitat
Original von herbivore
Das mit dem "rummogelt" war keine Kritik, zumal du dich ja nur um die Stellen "rumgemogelt" hast, an denen ich es auch getan habe.
(Hab ich auch nicht als solche angesehen) Durch AOp fallen diese halt weg 8)
Zitat
Ich sehe das Problem deshalb darin, dass man sie dem Transaktionsmanger übergeben kann und der so tut, als könne er.
Hm, und was sollte er stattdessen machen?
Zitat
RuntimeHelpers.GetHashCode. Damit sollte es dann immer klappen, wenn unsere oben angestellten Vermutungen richtig sind.
Tja dann sollte ich das wohl ändern...

Gruß
dN!3L
private Nachricht | Beiträge des Benutzers