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:

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

beantworten | zitieren | melden

Ist es möglich auch ohne manuelle Implementierung(z.B. via IClonable) eine Kopie eines Objektes zu erstellen? inklusive referenzierter Objekte?
Kann man das via binärer Serialisierung in einen MemoryStream und zurück lösen?
Oder ist das zu langsam bzw. hat irgendwelche anderen Macken?

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,

also erstmal ist Klonen vermutlich nicht das was du willst, was daran liegt, das Klonen fast nie das ist, was man will.

Es gibt keinen mir bekannten Grund, warum man Objekte, die es in der Realität nur einmal gibt, im Programm mehrmals haben sollte. Im Gegenteil! Was es in der Realität nur einmal gibt, sollte es im Programm ebenfalls nur einmal geben.

Sicher gibt es Objekte mit Wert-Semantik. Aber diese werden ja schon bei einer normlen Zuweisung "geklont". Dafür brauch man Klonen normalerweise also auch nicht.

Warum meinst du denn Objekte klonen zu müssen?

Aber abstrahieren wir mal von dem Sinn. Dann kann man mit Object.MemberwiseClone schonmal eine flache Kopie eines Objekts erzeugen.

Die Serialisierung ist (bis auf [NonSerialized]) keine schlechte, aber eine langsame (wenn auch vermutlich akzeptabele) Möglichkeit. Allerdings solltest du auch in diesem Fall ICloneable (eben über Serializierung) implementieren.

herbivore

PS: Obwohl ich die obige Aussage gerne so absolut hätte stehen lassen wollen, sind mir doch noch Zwitter eingefallen, also Objekte mit Wertsemantik (sprich: ohne eigene Identität), die aber nicht als struct implementiert sind, nämlich ArrayList & Co.
private Nachricht | Beiträge des Benutzers
cadi
myCSharp.de - Member

Avatar #avatar-1786.jpg


Dabei seit:
Beiträge: 308
Herkunft: Hamburg

beantworten | zitieren | melden

hallo herbivore,

ein grund für das klonen kann sein, das man den status eines Objektes festhalten will.

Oder wenn man eine Kopie für Worker-Threads erstellen will, die jeder autarg das Objekt in seinem Kontext manipulieren darf, ohne das die Kopien der anderen Threads verändert werden dürfen.

Beispiel ist eine workflow, der asynchrone sub-workflows startet die alle eine Kopie des Root-Workflow-Kontextes bekommen (lokale Variablen, aktueller Ausführungsschritt etc... )

Also es gibt durchaus Anwendungen für das Klonen!


Gruß,
cadi
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 cadi,

die von dir beschriebenen Situationen waren mir bewußt. Allerdings halte ich auch hier Klonen nicht für das geeignete Mittel. Beim Klonen wird ja nicht nur der Zustand, sondern auch die Identität gedoppelt (oder härter: zerstört). Sprich der normale Referenzsematik-Vergleichoperator '==' liefert für das Objekt und seinen Klon nicht mehr true. Das finde ich nicht wünschenswert.

Wenn man in eine relationale Datenbank schreibt, muss man sich auch nicht darum kümmern, das mehrere Prozesse/Threads die gleiche Zeile (=Objekt) schreiben wollen und man muss sich auch nicht darum kömmern, wenn Änderungen doch nicht durchgeführt werden sollen. Jedenfalls sollte man sich darum nicht kümmern müssen. Für den ersten Fall gibt es den Isolation Level und für den zweiten Fall Rollback bzw. Commit. Oder kurz es gibt einen Transaktionsmechanismus.

Wenn man also mit verschiedenen Zuständen des gleichen Objekts abeiten will oder muss, braucht man m.E. einen Transaktionsmechanismus; braucht und sollte aber keine Klone verwenden!

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

Avatar #avatar-1786.jpg


Dabei seit:
Beiträge: 308
Herkunft: Hamburg

beantworten | zitieren | melden

Die identität spielt aber in meinem Beispiel keine Rolle mehr.

Es ist den Workerthreads eh nicht erlaubt auf den context eines anderen threads zuzugreifen. Sollen und brauchen sie auch nicht. Sie sollen mit einer lokalen Kopie arbeiten.
Im Prinzip ist der geklonte original Kontext eher ein Template für die geklonten Kontexte.

Ich bin zumindest sher zufrieden mit meinen geklonten objekten.

(btw. ich habe noch transaktionelle globale Variablen, die von allen Worker-Threads erreichbar sind. Durch das Synchonisieren geht aber Perfomance verloren. Daher die kopien der lokalen variablen.)
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 cadi,
Zitat
Die identität spielt aber in meinem Beispiel keine Rolle mehr.
Ich finde der Identität der Objekte wird leider viel zu wenig Beachtung geschenkt. Für mich ist die Identität eines Objekts genauso wichtig wie sein Zustand und sein Verhalten. Für mich sind Zustand, Verhalten und Identität so was wie die "Dreieinigkeit" der Objektorientierung. Ein Objekt setzt sich für mich also aus diesen drei Teilen gleichberechtigt zusammen.

Wenn man mehrere Zustände eines Objekts braucht, braucht man also nur Kopien eines der drei Teile des Objekts, eben des Zustandes. Wenn man also nur 1/3 eines Objekts mehrfach braucht, warum sollte man dann 2/3 oder gar 3/3 des Objekts kopieren?

Am schönsten wäre, wenn C# bzw. .NET dieses Modell direkt unterstützen würden. Also wenn man Isolation Level und Transaktionen direkt auf Objektebene zur Verfügung hätte. Solange das nicht so ist, kopiere ich eben den Zustand des Objekts innerhalb des Objekts statt das Objekt selbst. Und da man - so oder so - was implementieren muss (entweder das Kopieren des Zustands oder das Kopieren des Objekts), ist es auch nicht aufwändiger nur den Zustand zu kopieren.
Zitat
Es ist den Workerthreads eh nicht erlaubt auf den context eines anderen threads zuzugreifen.
Warum denn das nicht? Das ist doch gerade der große Vorteil von Threads gegenüber Prozessen.

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



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

Themenstarter:

beantworten | zitieren | melden

Danke für eure Antworten!

Was heisst, dass die Identität zerstört wird?

Ich brauche das, da ich einen Einstellungsdialog habe, bei dem die Änderungen nicht sofort auf das eigentliche Objekt durchschlagen sollen und dürfen, sondern erst nach manueller Bestätigung übernommen werden dürfen.

Wie sieht es aus, wenn ich ein array von Objekten oder eine ArrayList habe? Werden dann auch flache Kopien davon erstellt?
Ich denke, dass ich das über IClonable implementieren muss, oder?

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,
Zitat
Was heisst, dass die Identität zerstört wird?
Im Prinzip geht es hier um die Frage, ob etwas dasselbe oder das gleiche ist. Wenn man eine genaue Kopie eines Objekts (z.B. Versicherter) herstellt, ist die Kopie eben ein neues Objekt und nicht mehr dasselbe. Die beiden Objekte sind also nicht identisch. Was mal aber eigentlich will, ist aber nur von *demselben* Objekt mehrere Zustände zu verwalten.
Zitat
Ich brauche das, da ich einen Einstellungsdialog habe, bei dem die Änderungen nicht sofort auf das eigentliche Objekt durchschlagen sollen und dürfen, sondern erst nach manueller Bestätigung übernommen werden dürfen.
Einstellungen klingen nach Werten, also nach struct und nicht class.

Ansonsten ist das mit dem Durchschlagen genau das, was ich mit dem Isolation Level und dem Transaktionsmechanismus gemeint habe - den es aber leider (noch) nicht gibt.
Zitat
Wie sieht es aus, wenn ich ein array von Objekten oder eine ArrayList habe?
Was Clone bei Array oder ArrayList macht, hängt davon ab, ob die enthaltenen Objekte Wert- oder Referenzsemantik haben. Im ersten Fall werden die Objekte quasi mit geklont, im zweiten Fall nicht.
Zitat
Ich denke, dass ich das über IClonable implementieren muss, oder?
ICloneable ist besser als die Objekte "von Hand" zu kopieren. Aber bei ICloneable wird immer noch das Objekt kopiert, was schlecht ist, wenn es sich um Objekte mit Identität handelt. Am besten finde ich es, wenn solche Objekte selbst mehrere Zustände verwalten können.

Wenn es jedoch um Objekt ohne Identität geht, kann man diese natürlich auch klonen, wenn das nicht - wie bei structs - ohnehin bei jeder Zuweisung passiert.

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

Avatar #avatar-1786.jpg


Dabei seit:
Beiträge: 308
Herkunft: Hamburg

beantworten | zitieren | melden

Hallo Herbivore,

Zur identität:
Ok, sie spielt nicht keine Rolle mehr, sonder im gegenteil! Es soll explizit eine Kopie genutzt werden.
Zitat
Zitat:
Es ist den Workerthreads eh nicht erlaubt auf den context eines anderen threads zuzugreifen.


Warum denn das nicht? Das ist doch gerade der große Vorteil von Threads gegenüber Prozessen.

Naja... manchmal braucht man Dinge halt nicht. Warum muss ein asynchroner Prozess wissen, was ein anderer Prozess gerade macht?

Der Witz ist ja gerade, das die völlig losgelöst von einander arbeiten. Eben nur mit einer gemeinsamen Ausgangsbasis.


Glaub es oder nicht, es macht Sinn....


/cadi
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 cadi,
Zitat
Glaub es oder nicht, es macht Sinn....
Das was du als letztes geschrieben hast, glaube ich dir ja. Insbesondere (wie du schreibst) Prozesse können schön unabhängig nebeneinanderher arbeiten.

Bei Threads (und darum ging ja die Frage) wird zumindest der Start automatisch synchronisiert (der Thread läuft erst los, wenn man ihn explizit startet) und das Ende sollte synchronisiert werden. Ich will auch zugestehen, dass es Threads geben kann, die ohne weitere Synchronisation nebeneinanderher arbeiten - aber der Normalfall wird das m.E. nicht sein.

Was aber noch viel wichtiger ist: Du hast geschrieben, dass es "den Workerthreads eh nicht erlaubt auf den context eines anderen threads zuzugreifen". Und das stimmt nun nicht. Darauf bezog sich meine Nachfrage.

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



Dabei seit:
Beiträge: 962
Herkunft: Rostock

beantworten | zitieren | melden

Hi Quallo,

ist der richtige Weg, mach es so.

Beim Serialisieren per Serializable-Attribut wird eine tiefe Kopie erstellt. Es sollte problemlos möglich sein, ein ICloneable-Interface für das Einstellungs-Objekt per Serialisierung zu implementieren, ist nicht zu langsam und hat auch keine Macken. Mit Arrays und ArrayList gibt es auch keine Schwierigkeiten, die implementieren Serializable. Auch deren Elemente werden tief kopiert.

D.h. du kannst im Einstellungsdialog eine Schattenkopie der Einstellungen erzeugen und vom Benutzer editieren lassen. Klickt er im Anschluss "Übernehmen", werden die geänderten Einstellungen übernommen, klickt er "Abbrechen", werden die alten Werte beibehalten. So war es gedacht oder?

Du schreibst im Startpost, dass es ohne ICloneable gehen soll. Geht natürlich auch. Dann muss eine andere Klasse die Kopie erstellen. Muss man sehen wo die Funktionalität am besten aufgehoben ist. Je nach Fall kann es sinnvoll sein, Operationen auf einer Klasse ausserhalb der Klasse zu implementieren.


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



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

Themenstarter:

beantworten | zitieren | melden

Danke für die Ermutigung. Dann werde ich das wohl per binärer Serialisierung machen, da denke ich, dass ich am wenigsten Probleme bekommen werde! Oder ist eine andere Methode schneller? Ich denke mal nicht, oder?

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

Avatar #avatar-1723.jpg


Dabei seit:
Beiträge: 21

beantworten | zitieren | melden

Kann man auch ohne Serialisierung tief kopieren?
Ein Control z.B. ist ja nicht Serialisierbar und hat auch keine ICloneable implementiert.
private Nachricht | Beiträge des Benutzers
cadi
myCSharp.de - Member

Avatar #avatar-1786.jpg


Dabei seit:
Beiträge: 308
Herkunft: Hamburg

beantworten | zitieren | melden

Hallo bayeror,

Im prinzip ja, hängt aber im einzelnen vom object ab, das kopiert werden soll.
Wenn es möglich ist, durch reines kopieren aller Felder den kompletten state sauber zu kopieren ann geht das per reflection.
Das funktioniert aber zum Beispiel nicht, wenn der Quelltyp keinen parameterlosen Konstruktor hat (woher soll ein Kopiermechanismus wiessen, welche Felder evtl. die Parameter des Konstruktors beinhalten?)

Gefährlich wird es auch bei objekten, die thread-spezifische Felder haben (z.B. der StringBuilder mit dem Feld m_currentThread. Ein Kopieren über Threadgrenzen kann da unvorhersehbare Folgen haben).

Du kannst ja mal mit dem folgenden Kode experimentieren.

Für produktiven Einsatz würde ich das aber nur unter erheblichen Vorbehalten empfehlen. Da musst du schon sehr genau wissen was du da machst....


using System;
using System.Reflection;

namespace CodeProject
{
	public class Cloner
	{
		public static object Clone(object source)
		{
			return Clone(source,false);	
		}
		public static object Clone(object source, bool useIClonableForFields)
		{
			object result = null;
			Type srcType = source.GetType();

			// create target
			// will throw an exception of no parameterless constructor is available
			result = srcType.Assembly.CreateInstance(srcType.FullName);

			// get a list of all instance fields
			FieldInfo[] fields = srcType.GetFields(BindingFlags.Instance | BindingFlags.Public |BindingFlags.NonPublic);

			// copy fields from source to result
			foreach(FieldInfo fieldInfo in fields)
			{
				object fieldValue = fieldInfo.GetValue(source);
				// clone value?
				if ( (fieldValue is ICloneable) && useIClonableForFields )
				{
					// set result's field value to cloned source value
					fieldInfo.SetValue(result,((ICloneable)fieldValue).Clone());
				}
				else
				{
					// set result's field value to source value
					fieldInfo.SetValue(result,fieldValue);
				}
				
			}
			return result;
		}
	}
}
private Nachricht | Beiträge des Benutzers
bayeror
myCSharp.de - Member

Avatar #avatar-1723.jpg


Dabei seit:
Beiträge: 21

beantworten | zitieren | melden

Dankschön!
Das dürfte das richtige sein.
private Nachricht | Beiträge des Benutzers
Programmierhans
myCSharp.de - Experte

Avatar #avatar-1651.gif


Dabei seit:
Beiträge: 4318
Herkunft: Zentralschweiz

beantworten | zitieren | melden

Moderationshinweis von herbivore (23.09.2013 - 14:26:14):

Der folgende Code ist eine (in Rekordzeit erstellte) Skizze. Eine etwas weiter ausgearbeitete Variante findet sich weiter unten.


Bei einer interessanten Diskussion mit herbivore sind wir auf das folgende Pattern gekommen, welches die volle Idendität erhält.

Das Interface für jede Datenklasse


using System;

namespace RollbackSample
{
	public interface IRollback
	{
		void StartTransaction();
		void CommitTransaction();
		void RollbackTransaction();
	}
}

Die Standard-Implementierung



using System;
using System.Collections;
using System.Reflection;

namespace RollbackSample
{
	public class RollbackBase : IRollback
	{
		private Hashtable _Properties;
		private Hashtable _IRollbacks;
		public RollbackBase()
		{
		}

		~RollbackBase()
		{
			if (this._Properties!=null)
			{
				this.RollbackTransaction();
			}
		}

		#region IRollback Members

		public void StartTransaction()
		{
			if (this._Properties!=null)
			{
				throw new Exception("Transaction pending");
			}

			this._Properties=new Hashtable();
			this._IRollbacks=new Hashtable();
			
			Type typ=this.GetType();
			PropertyInfo[] props=typ.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.Public);
			foreach (PropertyInfo prop in props)
			{
				if (prop.PropertyType.GetInterface("IRollback")==null)
				{
					//den Wert speichern wir mal
					this._Properties.Add(prop,prop.GetValue(this,null));
				}
				else
				{
					this._IRollbacks.Add(prop,prop.GetValue(this,null));
					//nächste Stufe ankicken
					IRollback ir=prop.GetValue(this,null)as IRollback;
					ir.StartTransaction();
				}
			}
		}
		
		public void CommitTransaction()
		{
			if (this._Properties==null)
			{
				throw new Exception("No Transaction started");
			}
			foreach (PropertyInfo prop in this._IRollbacks.Keys)
			{
				IRollback ir=(IRollback)this._IRollbacks[prop];
				ir.CommitTransaction();
			}
			this._Properties=null;
			this._IRollbacks=null;
		}

		public void RollbackTransaction()
		{
			if (this._Properties==null)
			{
				throw new Exception("No Transaction started");
			}
			foreach (PropertyInfo prop in this._Properties.Keys)
			{
				prop.SetValue(this,this._Properties[prop],null);
			}
			foreach (PropertyInfo prop in this._IRollbacks.Keys)
			{
				IRollback ir=(IRollback)this._IRollbacks[prop];
				ir.RollbackTransaction();
			}
			this._Properties=null;
			this._IRollbacks=null;
		}
		#endregion
	}
}




Eine Datenklasse:



using System;

namespace RollbackSample
{
	public class Beispiel : RollbackBase
	{
		private string _Name;
		private string _Vorname;
		private BeispielKlein _BeispielKlein=new BeispielKlein();

		public Beispiel()
		{
		}

		public string Name
		{
			get{return this._Name;}
			set{this._Name=value;}
		}

		public string Vorname
		{
			get{return this._Vorname;}
			set{this._Vorname=value;}
		}

		public BeispielKlein BeispielKlein
		{
			get{return this._BeispielKlein;}
		}
		
	}
}


Eine ChildDaten-Klasse



using System;

namespace RollbackSample
{
	public class BeispielKlein : RollbackBase
	{
		private string _Hobby;
		public BeispielKlein()
		{
		}
		public string Hobby
		{
			get{return this._Hobby;}
			set{this._Hobby=value;}
		}
	}
}



und noch ein wenig TestCode:



Beispiel bsp=new Beispiel();
			bsp.Name="Meier";
			bsp.Vorname="Hans";
			bsp.BeispielKlein.Hobby="A";
			System.Diagnostics.Debug.WriteLine(string.Concat(bsp.Name, " " ,bsp.Vorname, " ",bsp.BeispielKlein.Hobby));
			bsp.StartTransaction();
			bsp.Name="Müller";
			bsp.Vorname="Hugo";
			bsp.BeispielKlein.Hobby="B";
			System.Diagnostics.Debug.WriteLine(string.Concat(bsp.Name, " " ,bsp.Vorname, " ",bsp.BeispielKlein.Hobby));
			bsp.RollbackTransaction();
			System.Diagnostics.Debug.WriteLine(string.Concat(bsp.Name, " " ,bsp.Vorname, " ",bsp.BeispielKlein.Hobby));
			


Was meint ihr dazu ?

Programmierhans
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
private Nachricht | Beiträge des Benutzers
VizOne
myCSharp.de - Member

Avatar #avatar-1563.gif


Dabei seit:
Beiträge: 1551

beantworten | zitieren | melden

Gefällt mir! Vor allem, dass die Benutzung so transparent ist. Man muss nur vor unendlicher Rekursion aufpassen, wenn im Objektgraph ein Untergeordnetes Objekt eine Referenz auf ein Übergeordnetes Objekt enthält. Entweder man führt hinzu eine Liste, welche Objekte bereits StartTransaction empfangen haben, oder man geht schaut nach, ob _Properties schon != null. Wobei bei letzterem natürlich nicht mehr zu unterscheiden ist, ob eine Transaktion mehrfach gestartet worden ist, oder ob das gleiche Objekt bereits einen StartTransaction-Auftrag erhalten hat.


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

Avatar #avatar-1651.gif


Dabei seit:
Beiträge: 4318
Herkunft: Zentralschweiz

beantworten | zitieren | melden

Genau um so Spezialfälle abzudecken ist alles gegen Interface programmiert :-)

Denkbar wäre auch eigene Attribute zu verwenden so ala [ISupportRollback] oder besser eine verneinende Form welche auf denjenigen Members angebracht wird, welche nicht berührt werden sollen...

Eine prüfung ob this == dem Property des Childs entspricht würde direkte Child-Parent-Beziehungen verschonen...

Wege gibt es viele

Man kann IRollback auch noch mit einer TransactionID (vorschlag Type Guid) versehen und jedes SubObjekt über das Interface auf eine allfällig laufende Transaction (ID) überprüfen usw....

Es ist alles offen.
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
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 zusammen,

in dem o.g. Gespräch zwischen Programmierhans und mir, hatte ich ihn gefragt "kennst du eigentlich eine möglichkeit den zustand eines objekt zu sichern und dann in dasselbe objekt zurückzuspielen?" und das noch präzisiert mit "also was ich letztendlich will, sind transaktionen auf objektebene. man sagt sowas wie start transaction, dann ändert man lustig und sagt am schluss commit oder rollback. aber eben nicht für die datenbank, sondern für die objekte im speicher".

Was er daraufhin vorschlug, deckte sich ziemlich genau mit dem, was ich schon überlegt hatte - nur hatte ich gehofft, dass es noch was besseres gäbe. Naja, nachdem wir keinen besseren Ansatz gefunden haben, hat Programmierhans in nur 37 Minuten den obigen Code erstellt.

Respekt!

Das bei einer so kurzen Entwicklungszeit noch nicht alles berücksichtigt werden/sein kann, ist klar. So gibt es denn auch folgenden Probleme mit dem Code:

Es werden nur die öffentlichen Eigenschaften gesichert. Dabei klappt das Zurückspielen nicht, wenn eine Eigenschaft nur einen Getter hat oder läuft schräg, wenn Getter und Setter "asymmetrisch" sind, wie bei:


public int Murx 
{ 
   get { return _iMurx; } 
   set { _iMurx = value + 1; } 
}
Außerdem wird dadurch nur ein Teil des Zustandes des Objekts gesichert, z.B. werden Get- Und Set-Methoden nicht erfasst, da diese keine Eigenschaften sind.

Das alles kann man lösen, indem man alle Felder und nicht die Eigenschaften sichert.

Ich habe leider erst jetzt Zeit gefunden, meine Sicht der Dinge in Code zu gießen. Diese enthält zusätzlich noch folgende Verbesserungen:

Bei rekursiven Strukturen kommt es nicht mehr zu einer Exception. Eine Exception kommt aber weiterhin, wenn ein Objekt schon in einer *anderen* Transaktion enthalten ist. Zum erkennen, zu welcher Transaktion ein Objekt gehört, habe ich Programmierhans Vorschlag einer Guid aufgegriffen. Darüberhinaus wird sichergestellt, dass eine Transaktion für das Objekt beendet wird/werden muss, für dass sie auch begonnen wurde (Wurzelobjekt). Man kann also keine Unter- oder Teilstrukturen mehr committen.

Ein Umstand ist Programmierhans so richtig durch die Lappen gegangen: Man kann ja nicht nur den Zustand eines Business-Unterobjekts verändert, sondern es auch ganz austauschen (Objekt B statt Objekt A eingetragen). Zum einen wurde dann bei einem Rollback das Objekt nicht zurückgetauscht (wieder A eingetragen), zum anderen wurde A selbst gar nicht zurückgesetzt. Außerdem kam es zu einer Exception wenn bzw. weil Bs _Properties-Eigenschaft null ist. Analog für Commit.

All dies ist behoben, indem alle Objekte in einer Transaktion in der Liste _listScope gespeichert werden. Dadurch fällt auch das ausgetauschte Objekt A nicht versehentlich dem CG in die Hände.

Zu guter Letzt sind auch die Probleme, die der Destruktor verursachen konnte, behoben, da jetzt das erste Objekt einer Transaktion, dass der GC bearbeitet zu einem Rollback der gesamten Transaktion (beginnend an der Wurzel) führt.

Eins konnte ich aber auch nicht lösen. Nur bei Feldern mit Werttypen (Int32) oder Readonly-Typen (z.B. String), wird ja echt der Zustand gesichert. Bei Referenztypen (außer BusinessObjects natürlich) wird nur gesichert, welches Objekt eingetragen war (und beim Rollback dieses Objekt wieder eingetragen). Wenn sich aber der Zustand dieses Objekts geändert hat, kann und wird dieser durch das Rollback nicht wiederhergestellt.

Programmierhans hielt nach dem Produkthaftungsgesetz folgenden Hinweis für erforderlich: Databinding ist in der BusinessObject-Klasse nicht berücksichtigt. :-)

Hier kommt der Code (der Einfachheit halber in einem Rutsch). Auch ich würde mich über Kommentare und Rückmeldungen freuen.


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

//*****************************************************************************
public abstract class BusinessObject
{
   //--------------------------------------------------------------------------

   // In welcher Transaktion das Objekt enthalten ist
   private Guid                           _guidTransaction = Guid.Empty;

   // Bei welchem Objekt die Transaktion begonnen wurde
   private BusinessObject                 _boRoot         = null;

   // Alle Objekte in der Transaktion; nur beim WurzelObjekt gesetzt
   private List <BusinessObject>          _listScope      = null;

   // Der gespeicherte Zustand des Objekts
   private Dictionary <FieldInfo, Object> _dictFields     = null;

   //==========================================================================
   public void StartTransaction ()
   {
      if (_listScope != null) {
         throw new Exception ("Keine geschachtelten Transaktionen erlaubt.");
      }

      _listScope = new List <BusinessObject> ();

      try {
         _StartTransaction (0, Guid.NewGuid (), this);
      }
      catch (Exception) {
         foreach (BusinessObject boCurr in _listScope) {
            boCurr._guidTransaction = Guid.Empty;
            boCurr._boRoot          = null;
            boCurr._dictFields      = null;
         }
         _listScope = null;
         throw;
      }
   }

   //==========================================================================
   private void _StartTransaction (int iLevel,
                                   Guid guidTransaction,
                                   BusinessObject boRoot)
   {
      if (_guidTransaction != Guid.Empty) {
         if (_guidTransaction == guidTransaction) {
            return; // zyklischen Struktur, Objekt schon besucht
         }
         throw new Exception ("Objekt ist schon in einer anderen "
                            + "Transaktion enthalten.");
      }

      boRoot._listScope.Add (this);
      _guidTransaction = guidTransaction; // als besucht kennzeichnen
      _boRoot = boRoot;
      _dictFields = new Dictionary <FieldInfo, Object> ();

      FieldInfo [] afi;
      List <FieldInfo> listfiAll = new List <FieldInfo> ();
      BusinessObject boChild;

      Type t;

      // Die Schleife ist notwending, weil man sonst nicht an die privaten
      // Felder der Oberklassen kommt.
      for (t = GetType (); t != typeof (BusinessObject); t = t.BaseType) {
         afi = t.GetFields (BindingFlags.GetField
                          | BindingFlags.Instance
                          | BindingFlags.NonPublic
                          | BindingFlags.DeclaredOnly
                          | BindingFlags.Public); // nur zur Sicherheit
         foreach (FieldInfo fi in afi) {
            #if SW_VERBOSE
            Console.WriteLine (new String (' ', 3 * iLevel)
                               + fi.DeclaringType + "." + fi.Name);
            #endif
            _dictFields [fi] = fi.GetValue (this);
         }
         listfiAll.AddRange (afi);
      }

      // Rekursiver Abstieg
      foreach (FieldInfo fi in listfiAll) {
         if (fi.FieldType.IsSubclassOf (typeof (BusinessObject))) {
            #if SW_VERBOSE
            Console.WriteLine ("=> " + new String (' ', 3 * iLevel)
                               + fi.DeclaringType  + "." + fi.Name);
            #endif
            boChild = (BusinessObject)fi.GetValue (this);
            if (boChild != null) {
               boChild._StartTransaction (iLevel + 1, guidTransaction, boRoot);
            }
         }
      }
   }

   //==========================================================================
   public void CommitTransaction ()
   {
      if (_guidTransaction == Guid.Empty) {
         throw new Exception ("Objekt ist in keiner Transaktion enthalten.");
      }

      if (_listScope == null) {
         throw new Exception ("Die Transaktion wurde nicht bei diesem "
                            + "Objekt begonnen");
      }

      foreach (BusinessObject boCurr in _listScope) {
         boCurr._guidTransaction = Guid.Empty;
         boCurr._boRoot          = null;
         boCurr._dictFields      = null;
      }
      _listScope = null;
   }

   //==========================================================================
   public void RollbackTransaction ()
   {
      if (_guidTransaction == Guid.Empty) {
         throw new Exception ("Objekt ist in keiner Transaktion enthalten.");
      }

      if (_listScope == null) {
         throw new Exception ("Die Transaktion wurde nicht bei diesem "
                            + "Objekt begonnen");
      }

      foreach (BusinessObject boCurr in _listScope) {
         #if SW_VERBOSE
         Console.WriteLine (boCurr.GetType ());
         #endif
         foreach (FieldInfo fi in boCurr._dictFields.Keys) {
            #if SW_VERBOSE
            Console.WriteLine ("..."  + fi.DeclaringType + "." + fi.Name);
            #endif
            fi.SetValue (boCurr, boCurr._dictFields [fi]);
         }
         boCurr._guidTransaction = Guid.Empty;
         boCurr._boRoot          = null;
         boCurr._dictFields      = null;
      }
      _listScope = null;
   }

   //==========================================================================
   ~BusinessObject ()
   {
      if (_listScope != null) {
         RollbackTransaction ();
      } else if (_boRoot != null) {
         _boRoot.RollbackTransaction ();
      }
   }
}

//*****************************************************************************
public class Beispiel : BusinessObject
{
   private   string         _strName     = "";
   protected string         _strVorname  = "";
   public    string         _strTitel    = ""; // zu Testzwecken public
   private   DateTime       _dtGebDat;
//   private   readonly int   _i           = 5;
   private   Beispiel       _bspZyklisch = null;
   private   BeispielKlein  _bspklNull   = null;
   private   BeispielKlein  _bspklStufe1 = new BeispielKlein ();
   private   BeispielGeerbt _bspklStufe2 = new BeispielGeerbt ();

   //==========================================================================
   public Beispiel ()
   {
      _bspZyklisch = this;
   }

   //==========================================================================
   public String Name
   {
      get { return _strName; }
      set { _strName = value; }
   }

   //==========================================================================
   public String Vorname
   {
      get { return _strVorname; }
      set { _strVorname = value; }
   }

   //==========================================================================
   public String Titel
   {
      get { return _strTitel; }
      set { _strTitel = value; }
   }

   //==========================================================================
   public DateTime GebDat
   {
      get { return _dtGebDat; }
      set { _dtGebDat = value; }
   }

   //==========================================================================
   public BeispielKlein BeispielKlein
   {
      get { return _bspklStufe1; }
      set { _bspklStufe1 = value; }
   }

   //==========================================================================
   public BeispielGeerbt BeispielGeerbt
   {
      get { return _bspklStufe2; }
      set { _bspklStufe2 = value; }
   }

   //==========================================================================
   public override String ToString ()
   {
      return "{ "
            + String.Join (" | ", new String [] { _strName,
                                                  _strVorname,
                                                  _strTitel,
                                                  _dtGebDat.ToString () } )
            + Environment.NewLine + "   "
            + (_bspZyklisch == null ? "<null>" : (_bspZyklisch == this
                                                ? "<this>"
                                                : _bspZyklisch.ToString ()))
            + Environment.NewLine + "   "
            + (_bspklNull == null ? "<null>" : _bspklNull.ToString ())
            + Environment.NewLine + "   "
            + (_bspklStufe1 == null ? "<null>" : _bspklStufe1.ToString ())
            + Environment.NewLine + "   "
            + (_bspklStufe1 == null ? "<null>" : _bspklStufe2.ToString ())
            + Environment.NewLine
            + "}";
   }
}


//*****************************************************************************
public class BeispielKlein : BusinessObject
{
   private   string _strPrivateHobby = "";
   protected string _strProtectedHobby = "";

   //==========================================================================
   public String PrivateHobby
   {
      get { return _strPrivateHobby; }
      set { _strPrivateHobby = value; }
   }

   //==========================================================================
   public String ProtectedHobby
   {
      get { return _strProtectedHobby; }
      set { _strProtectedHobby = value; }
   }

   //==========================================================================
   public override String ToString ()
   {
      return "{ "
            + String.Join (" | ", new String [] { _strPrivateHobby,
                                                  _strProtectedHobby } )
            + "}";
   }
}

//*****************************************************************************
public class BeispielGeerbt : BeispielKlein
{
   private String _strSport;

   //==========================================================================
   public String Sport
   {
      get { return _strSport; }
      set { _strSport = value; }
   }

   //==========================================================================
   public override String ToString ()
   {
      return "{ "
            + String.Join (" | ", new String [] { base.ToString (),
                                                  _strSport } )
            + "}";
   }
}

//*****************************************************************************
abstract class App
{
   public static void Main (string [] astrArg)
   {
      Beispiel bsp = new Beispiel();
      bsp.Name = "Meier";
      bsp.Vorname = "Hans";
      bsp.GebDat  =  DateTime.Parse ("12.12.1912");
      bsp.BeispielKlein.PrivateHobby = "priv A";
      bsp.BeispielKlein.ProtectedHobby = "prot A";
      bsp.BeispielGeerbt.PrivateHobby = "priv AA";
      bsp.BeispielGeerbt.ProtectedHobby = "prot AA";
      bsp.BeispielGeerbt.Sport = "AAA";

      Console.WriteLine (bsp);

      bsp.StartTransaction();
      bsp.Name = "Müller";
      bsp.Vorname = "Hugo";
      bsp.BeispielKlein.PrivateHobby = "priv B";
      bsp.BeispielKlein.ProtectedHobby = "prot B";
      bsp.BeispielGeerbt.PrivateHobby = "priv BB";
      bsp.BeispielGeerbt.ProtectedHobby = "prot BB";
      bsp.BeispielGeerbt.Sport = "BBB";
      bsp.BeispielKlein = new BeispielKlein ();
      bsp.BeispielKlein.PrivateHobby = "priv C";
      bsp.BeispielKlein.ProtectedHobby = "prot C";

      Console.WriteLine (bsp);
      bsp.RollbackTransaction();

      Console.WriteLine (bsp);

      Beispiel bsp2 = new Beispiel();
      bsp2.StartTransaction();
   }
}
herbivore

PS: In "Zeiger" oder Kopie gibt es eine Variante von codester, die auch Collections berücksichtigt/behandelt.
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

Was mir jetzt im Nachhinein erst auffällt ist, dass Du leider das Interface rausgenommen hast.... so ist man gezwungen von Deiner Klasse abzuleiten. Da die Methoden aber nicht virtual sind wird's übel wenn man noch aus welchen Gründen auch immer was dazwischenklemmen muss.

Allenfalls könnte man den rekursiven Abstieg vorziehen... so kannst Du schneller erkennen wenn ein SubObjekt nicht gesperrt werden kann.... Da ja dann eh die ganze Transaktion nichtig ist, müsstest Du nirgendwo Daten einlesen...

Je länger man sich mit der Materie beschäftigt, desto mehr Details fallen halt auf.... aber dies ist ja der Grund wieso man über den Code diskutiert... jeder sieht es aus seiner Perspektive... und mehr Augen sehen nun mal mehr.

Gruss
Programmierhans
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
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,

das mit dem Interface ist Absicht. Habe ich vergessen zu schreiben, obwohl mir das wichtig war. Ich glaube das gäbe Murx, wenn verschiedene Implementierungen von StartTransaction in einer Transaktion zusammnentreffen.

Trotzdem ich Fan von Vererbung bin, habe ich die Exemplarvariablen von BusinessObject absichtlich private gemacht. Aus dem gleichen Grund, um inkompatible Implementierungen zu verhinden. Daher ist es auch passend, wenn die Methoden von BusinessObject nicht virtual sind (auch wenn ich das nur zufällig so gemacht habe).

Aber vielleicht überzeugt mich auch jemand vom Gegenteil!

Das mit dem Vorziehen des rekursiven Abstiegs ist eine gute Idee. Ich hatte das Speichern der Felder und den rekursiven Abstieg schon extra getrennt, obwohl es ja einfacher gewesen wäre das zu mischen. Aber auf die Idee den Abstieg vorzuziehe bin ich nicht gekommen. Super!

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

Avatar #avatar-2167.png


Dabei seit:
Beiträge: 4649
Herkunft: Riegel am Kaiserstuhl

beantworten | zitieren | melden

Hi,

nach einem sehr informativen und ergiebigen Telefonat mit Herbivore habe ich noch folgende Anregung:

Es wäre praktisch, wenn man mehrere Root-BOs in eine Transaktion zusammen fassen könnte ... dies wäre quasi eine Art Transaktionshandler, der die einzelnen Root-BOs verwaltet.

Vorstellen würde ich mir also, dass zunächst am Transaktionshandler eine globale Transaktion gestartet wird, an diesem dann die einzelnen Root-BOs registriert werden, die an der Transaktion teilnehmen sollen, was einen automatischen Start deren Transaktion zur Folge hat.

Setzt man dann auf dem Transaktionshandler ein Commit oder ein Rollback ab, so muss dieser lediglich hingehen, und auf allen registrierten Objekten die entsprechende Methode auslösen. Zu beachten ist dabei allerdings, dass der Handler von außen eine GUID vorgibt, damit alle Objekte in der Transaktion als Objekte derselben Transaktion erkannt werden.

Frage wäre hier, ob es Sinn ergibt, mehrere Transaktionen pro Handler zu ermöglichen (IMHO nein). Falls nämlich nicht, könnte dieser als Singleton implementiert werden.

Any further comments?

Viele Grüße,


der Eisbär
Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de
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 Der Eisbär,

den Vorschlag, mehrere Objekte in eine Transaktion zu packen, finde ich sehr sinnvoll. Vielen Dank dafür.

Ich habe mal eine Variante implementiert, die ohne einen extra Transaktionshandler auskommt und auch sonst minimalinvasiv ist. Man kann einfach weitere Objekte über das Wurzelobjekt einer Transaktion zu eben dieser Transaktion hinzufügen, also z.B.:


bsp.StartTransaction ();
bsp.AddToTransaction (bsp2);
Es ist schon spät und ich hoffe ich habe keine Fehler eingebaut, aber m.E. müsste es reichen zu dem Code von oben, AddToTransaction mit folgender Implementierung hinzuzufügen:


public void AddToTransaction (BusinessObject boToAdd)
{
   if (_listScope == null) {
      throw new Exception ("Objekte können nur über das Wurzelobjekt "
                         + "einer Transaktion hinzugefügt werden.");
   }

   int iListScopeCount = _listScope.Count;

   try {
      boToAdd._StartTransaction (1, _guidTransaction, this);
   }
   catch (Exception) {
      BusinessObject boCurr;
      for (int i = _listScope.Count - 1; i ≥ iListScopeCount; --i) {
         boCurr = _listScope [i];
         boCurr._guidTransaction = Guid.Empty;
         boCurr._boRoot          = null;
         boCurr._dictFields      = null;
         _listScope.RemoveAt (i);
      }
      throw;
   }
}
Du kannst ja mal schreiben, ob dir das besser, auch oder gar nicht gefällt.

herbivore
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 Der Eisbär,

wie wir gestern Abend noch besprochen haben, kommt man mit AddToTransaction auch billig zu dem von dir gewünschten Transaktionshandler. Dazu implementiert man einfach eine leere Klasse TransactionsHandler, die von BusinessObject erbt, und startet Transaktionen per Konvention nur über Objekte dieser Klasse. Ob man den Transaktionshandler als komlett leere Klasse oder als Singleton implementiert, kann man sich natürlich auch aussuchen.

Die Guid von außen ist m.E. gar nicht nötig. Das Transaktionshandler-Objekt bekommt ja bei StartTransaction eine Guid verpasst. Wenn weitere Objekte mit AddToTransaction hinzugefügt werden, wird diese Guid an sie weitergegeben.

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



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

Themenstarter:

beantworten | zitieren | melden

Wäre es nicht praktischer, das Ganze etwas anders aufzuziehen?

Ich finde es schwach, wenn ich extra ein Interface implementieren muss oder von der Klasse ableiten muss.
Das hemmt die Benutzung und die Akzeptanz eines solchen Mechanismus.
Ich würde das viel lieber als eine Liste haben, etwa so:


TransActionList<T> : IList
{
    //Hier kann ich Objekte hinzufügen per Add oder Addrange(Intern kann ja einfach eine List<T> benutzt werden).

   public void StartTransaction()

   public void Commit();

   public void Rollback();
}
In diese Liste kann ich dann beliebige Objekte hineinpacken und Transactionen darauf anwenden.

Wenn ich etwas aus der Liste heraushole, bekomme ich eine flache Kopie mit dem aktuellen Stand eines Objekts.

Meine Vorschläge hier sind sicherlich noch nicht ausgereift, aber zwei Kernaussagen habe ich:

1. Das soll auf jedes beliebige Objekt anwendbar sein!
2. Das soll auf einem allgemeingültigen Standard(wie zum Beispiel IList) aufbauen.

Beides zusammen bedeutet super Usability(geiles Marketinggeblubber, was? *g*) und
eine allgemeine Anwendbarkeit!
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 denke, das Erben von BusinessObject ist bei Bussines-Objekten keine Einschränkung. Bei meinen größeren Anwendungen gibt es ohnehin so eine Oberklasse. Dann kann sie auch das Transactionhandling übernehmen.

Allerdings können durch den bisherigen Ansatz nur Business-Objekte "transaktioniert" werden. Das ist schon eher der Nachteil, für den dein Vorschlag eine Lösung aufzeigt.

Ich habe darauf hin mal in diese Richtung überlegt. Das zeigt sich z.B. durch meine Anfrage Technische Identität (Adresse) ermitten? Denn wenn man sich den Zustand nicht *in* dem Objekt merkt (_dictFields), muss man ihn sich *zu* dem Objekt merken und das geht m.E. nur über seine (technische) Identität. Leider bin ich momentan noch nicht weiter gekommen, weil ich mich mit einer merkwüdigen Exception rumärgere und noch nicht die Zeit und Ruhe hatte, sie zu knacken. Aber selbst ohne die Exception muss man ziemlich weit runter auf GC-Ebene und das gefällt mir nicht.
Zitat
Wenn ich etwas aus der Liste heraushole, bekomme ich eine flache Kopie mit dem aktuellen Stand eines Objekts.
Na das nun gerade nicht! Dann kann man auch klonen. Mir geht es ja gerade darum Kopien, von *Objekten* zu vermeiden und nur den *Zustand* zu kopieren. Außerdem hat man ja nicht gewonnen, wenn man den *aktuellen* Stand rausholt. Den aktuellen Stand hat man ja eh im Objekt. Falls du den *alten* Stand meinst, hat das wiederum nichts mit Transaktionen zu tun, weil es bei denen nicht darum geht, den alten Stand *abzurufen* sondern *wiederherzustellen*.

Dein Vorschlag mach m.E. dann Sinn, wenn TransActionList<T> eine write-only-Liste ist oder wenn man beim Lesen die Originalobjekte (zwansgläufig im aktuellen Stand) bekommt (also keine Kopien - auf keinen Fall - Hände über dem Kopf zusammenschlag :-)

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



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

Themenstarter:

beantworten | zitieren | melden

Wieso soll das nur über die technische Identität gehen?
Ich kann mir doch einfach eine Referenz auf das Objekt speichern, auf das ich eine Transaktion anwenden will, oder wieso soll das nicht klappen?
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,

naja, darum geht es ja. Sinnvollerweise speichert man diese Referenz als Key eines Dictionary, damit man darüber schnell an den zugehörigen (gesicherten) Zustand des Objekts kommt. Wenn man aber einfach schreibt:

dictZustand [obj] = ...

dann wird eben nicht die Referenz des Objekts als Key verwendet, sondern (vereinfacht und nicht ganz korrekt) sein Hashcode. Außerdem soll es ja für beliebige Objekte gehen und dann ist nicht mal sichergestellt, dass GetHashCode vernüftig implementiert ist. Tja, und so bin ich quasi auf der Suche nach etwas, was die Referenz des Objekts als Zahl liefert. In dem anderen Thread steht ja dazu mehr.

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



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

Themenstarter:

beantworten | zitieren | melden

Brauch man wirklich ein Dictionary?
Das ist die Frage die man sich stellen sollte, falls man für das Problem keine Lösung findet!

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,

das habe ich schon überlegt.

Ich habe überlegt, ob man eine SortedList nehmen kann, aber da hat man das gleiche Problem, weil man dann nach Referenz sortieren können müsste (also einen größer-Vergleich bräuchte, man hat aber nur Gleichheit).

Was dann noch bleibt ist eine unsortierte Liste. Da hätte man kein Problem mit den Referenzen, aber dafür mit dem Aufwand, weil dieser dann statt linear quadratisch wird. Und das ist für eine allgemeingültige Lösung - wie du sie ja gerne hättest ;-) - m.E. nicht akzeptabel.

herbivore
private Nachricht | Beiträge des Benutzers