Laden...

Kopie ohne ICloneable [oder warum man Objekte nicht kopieren sollte; Transaktionen auf Objekten]

Erstellt von Quallo vor 18 Jahren Letzter Beitrag vor 16 Jahren 50.663 Views
2.921 Beiträge seit 2005
vor 17 Jahren
Serialisierung

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.

2.891 Beiträge seit 2004
vor 17 Jahren

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 👍

49.485 Beiträge seit 2005
vor 17 Jahren

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

2.891 Beiträge seit 2004
vor 17 Jahren

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?

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo dN!3L,

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

herbivore

2.891 Beiträge seit 2004
vor 17 Jahren

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)

49.485 Beiträge seit 2005
vor 17 Jahren

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.

2.891 Beiträge seit 2004
vor 17 Jahren

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.

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.

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.

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

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

PS: Wegen der Vorgehensweise beim Veröffentlichen eines Artikels schicke ich dir später noch eine PM. Sicher Mitte Februar dann...

49.485 Beiträge seit 2005
vor 17 Jahren

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

2.891 Beiträge seit 2004
vor 17 Jahren

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)

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?

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

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo dN!3L,

Hm, und was sollte er stattdessen machen?

man könnte die Klassen, die transaktionierbar sind, von einem Interface ITransactionable erben lassen und das Interface dann als Parmetertyp von RegisterObject verwenden. Das wäre dann eine Compilezeitüberprüfung. Oder man zeichnet die Klassen, die transaktionierbar sind, mit einem Attribut aus, dessen Vorhandensein man zur Laufzeit prüft.

Aber das beantwortet nur die theoretische Machbarkeit. Über die Praktikabilität habe ich mir keine Gedanken gemacht.

Bei dem BusinessObject-Ansatz hat man die transktionierbaren Klassen automatisch ausgezeichnet, eben indem sie von BusinessObject erben.

herbivore

52 Beiträge seit 2007
vor 16 Jahren
Transaktionen für transiente Objekte

hi herbivore, Quallo, cadi u. a.

Habe mir diesen thread durchgelesen und finde die Idee sagenhaft. 8) Transaktionen für transiente Objekte. Ich finde herbivore hat vollkommen Recht, selbst wenn ein Objekt unterschiedliche Zustände hat so muss es dafür nicht zwangsläufig seine Identität verlieren indem es z. B. kopiert wird. 🙁 Es hört sich zwar irgendwie logisch an dass wenn man ein Objekt zwecks Manipulation etwa in eine Dialogbox reinreicht dass man vorher eine Kopie erstellt weil der Benutzer eventuell "Cancel" anklickt um sämtliche Änderungen zu verwerfen, dann muss man natürlich den vorherigen Zustand wieder herstellen. Allerdings genügt es tatsächlich lediglich den Zustand zu sichern und nicht das komplette Objekt, ein Transaktionsmechanismus finde ich da die bessere Lösung. Es wäre wirklich schön wenn das in .NET schon enthalten wäre. =)

Seid ihr denn noch weiter gekommen bzw. was ist aus dieser Idee geworden? Das würde mich wirklich interessieren. 😉

yb~~~~~~~~~~~~

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo yetibrain,

die einzige Neuigkeit, die sich aus meiner Sicht ergeben hat, ist die Variante von codester in "Zeiger" oder Kopie , die auch Collections berücksichtigt/behandelt.

Mich hindert so ein bisschen am Weitermachen, dass es bestimme Grenzen gibt, an die man man relativ schnell stößt und die sich auch nicht überwinden lassen, z.B. wenn man an Dateien denkt oder noch besser an Netzwerkstreams. Gesendete Daten bleiben gesendet. Ein Wiederherstellen des alten Zustands beim Rollback ist nicht möglich.

Unabhängig davon gibt es eine Bibliothek von Ralf Westphal mit Namen NSTM. Siehe dazu Mein aktuelles Steckenpferd: Software Transactional Memory und Software Transactional Memory. Im Focus dieser Bibliothek liegt zwar der gleichzeitige Zugriff durch mehrere Threads und es wird dort intern auch mit Clone gearbeitet, aber es gibt natürlich trotzdem Transaktionen mit Commit und Rollback und mindestens einen Blick ist das auf alle Fälle wert.

herbivore