Laden...

Refelction Verschachtelung in Generischen Methoden

Erstellt von Craze89 vor 14 Jahren Letzter Beitrag vor 14 Jahren 1.838 Views
C
Craze89 Themenstarter:in
52 Beiträge seit 2008
vor 14 Jahren
Refelction Verschachtelung in Generischen Methoden

Hallo community,

Ich versuche gerade eine Klasse zu programmieren, die alle unterschiedlichen properties von 2 Objekten raussucht und diese als Liste zurück gibt.

Da das ganze aber für jeden Typ funktionieren soll (also generisch sein soll) probiere ich das ganze über reflection zu machen, aber ich komme bei den verschachtelungen nicht so ganz weiter.

public class HistorienCreater
	{
		public IList<HistorienDTO> CreateHistorie<T>(T oldItem, T newItem) {
			var historien = new List<HistorienDTO>();
			foreach (PropertyInfo prop in oldItem.GetType().GetProperties()) {

//				if (prop.GetType().GetProperties().Length > 1) {
//					historien.AddRange(CreateHistorie(oldItem.GetType().GetProperty(prop.Name), newItem.GetType().GetProperty(prop.Name)));
//				}
//				else {
					string oldValue = oldItem.GetType().GetProperty(prop.Name).GetValue(oldItem, null).ToString();
					string newValue = newItem.GetType().GetProperty(prop.Name).GetValue(newItem, null).ToString();
					if (!oldValue.Equals(newValue)) {
						historien.Add(new HistorienDTO(prop.Name, oldValue.ToString(), newValue.ToString()));
//					}
				}
			}
			return historien;
		}
	}

Das Problem ergibt sich in dem auskommentierten Bereich. Das ganze bildet einen Recursive Call, von dem mir auch klar ist, das und wie er zu stande kommt.

Aber wie kann ich herausfidnen ob "unterproperties" sich auch geändert haben, ohne einen recursive call zu verursachen?

Bsp.:
Ich habe ein Dto (TestClass) das ein weiteres Dto (TestClass2) beinhaltet:

public class TestClass
	{
		public string Property1 { get; set; }

		public string Property2 { get; set; }

		public TestClass2 Property3 { get; set; }
	}

	public class TestClass2
	{
		public int UProperty1 { get; set; }

		public int UProperty2 { get; set; }
	}

wie Finde ich herraus ob OldItem.Property3.UProperty2 != NewItem.Property3.UProperty2 ist, ohne die jeweiligen typen zu kennen?

Danke schonmal für eure Hilfe

MfG
Craze

3.971 Beiträge seit 2006
vor 14 Jahren

wie Finde ich herraus ob OldItem.Property3.UProperty2 != NewItem.Property3.UProperty2 ist, ohne die jeweiligen typen zu kennen?

Wenn es sich um Verweise handelt (und ich dich richtig verstanden habe), dann sollte Object.ReferenceEquals(obj1, obj2) dir helfen.

Das ganze bildet einen Recursive Call, von dem mir auch klar ist, das und wie er zu stande kommt.

Aber wie kann ich herausfidnen ob "unterproperties" sich auch geändert haben, ohne einen recursive call zu verursachen?

Du musst dir dazu in einer Liste (Scope) merken, wo du überall schon lange gekommen bist (wieder ReferenceEquals) und bei Bedarf den Vorgang mit der nächsten Property fortsetzen.

Da ich nicht genau weiß was du vorhast, schau dir mal folgenden Beitrag (sowie den kompletten Thread) Kopie ohne ICloneable an

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

C
Craze89 Themenstarter:in
52 Beiträge seit 2008
vor 14 Jahren

Hallo kleines_eichhoernchen

ok vielleicht habe ich mich etwas unklar ausgedrückt, ich versuchs nochmal zu erklären 😃

Also Wir gehen davon aus, wir haben ein Programm, in das ein DomainObject (oder BusinessObject) geladen wird, um dieses dann bearbeitbar zu machen, werden alle nötigen daten des DO, sagen wir mal eine Lieferadresse in ein DTO übertragen und eine kopie davon in den View geschoben. jetzt kommt der User und bastelt sich lustig seine neue Lieferadresse (ändert die straße und den Ort).

dann möchte ich das "original" DTO, also das was aus dem DO entstanden ist und das "kopierte" DTO aus dem view, an dem rumgebastelt wurde mit einander vergleichen. Was mich jetzt interessiert sidn die geänderten Properties. Bis hier hin ist alles ja noch recht simpel. ich kriege das ergebnis, das sich das Property "Straße" von "StraßeA"(original DTO) nach "StraßeB"(kopie) geändert hat und das Property "Ort" hat sich von "OrtA"(original) nach "OrtB"(kopie geändert). das funktioniert auch mit meinem code soweit schon, WENN das jeweilige property ein simpler typ ist (also string, int, bool und all sowas) sobald jetzt aber ein 2tes DomainObject (oder DTO o.ä.) als Property vorhanden ist, wird es schwierig, denn dann muss ich ja quasi eine ebene Tiefer in die Verschachtelung. Und hier liegt genau das problem.

Der thread den du mir genannt hast, hat mir da aber schonmal einen kleinen denkanstoß gegeben.wenn ich bei jedem schrit den ich tiefer in die verschachtelung eindringe den kompletten pfad speichere und dann überprüfe, ob ich da schonmal war ist das ganze natürlich nicht mehr recursive, ich weis allerdings nicht, ob das nicht toooo much dafür ist, denn Wie sieht das ganze aus, wenn der Typ der gerade verarbeitet wird ein DTO und z.B. zusätzlich noch eine Liste enthält. im Falle der Liste müsste ich ja auch über jeden eintrag der liste iterieren, der gegebenen falls ja auch wiederrum aus einem complexen typen bestehen kann 😃

3.971 Beiträge seit 2006
vor 14 Jahren

merk dir einfach in einem Dictionary<object, bool>, ob du die Instanz bereits untersucht hast. Wenn ja prüfe die nächste Property.

Aber ich glaub du machst es dir vllt. was schwer. Es gibt das Interface IPropertyChanged was du in deine Business-Klassen einbinden kannst. An das PropertyChanged-Event hängst du dich ran, fertig. Wenn du geschachtelte Business-Objekt-Klassen hast, musst du je nach Anwendungsfall ein eigenes Interface implementieren, um, das nach "oben" weiterzugeben (Bubble)

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

C
Craze89 Themenstarter:in
52 Beiträge seit 2008
vor 14 Jahren

da hast du durchaus recht, das problem ist, das ganze ist kein privates projekt, sondern etwas das ich für "meine" Firma entwickle und alle DomainObjects die wir haben nachträglich um zu bauen, ist doch sehr krass, nicht unmöglich, aaaber krass 😃

sonst wüsste ich auch shcon wie ich das ganze handhabe

aber ich hab auch mittlerweile einen lösungsansatz gefunden, so ganz klappt das allerdings auch noch nicht, aber vielleicht hat ja jemand noch ne idee, wie man das verbessern könnte.

und zwar mache ich das ganze jetzt so:

ich schmeiße meine 2 objekte in den creater und hole mir alle properties, über die ich dann iteriere, wenn es ein valuetype ist und sich der wert geändert hat, wird das ganze sofort in meine Liste geschrieben, wenn es sich um eine klasse handelt, wird ein neuer "subcreater" geöffnet, der das ganze weiter verarbeitet.

das ganze sieht jetzt so aus :

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

namespace Jjk.AboAdressenPool.HistorienCreater
{
	public class HistorienCreater
	{
		public IList<HistorienDTO> CreateHistorie<T>(T oldItem, T newItem) {
			var historien = new List<HistorienDTO>();
			foreach (PropertyInfo prop in oldItem.GetType().GetProperties()/*Hier gibt es einen StackOverflow*/) {

				if (prop.GetType().IsClass) {
					historien.AddRange(HistorieForClass(oldItem.GetType().GetProperty(prop.Name), newItem.GetType().GetProperty(prop.Name)));
				}
				else if (prop.GetType().IsValueType) {
					historien.AddRange(HistorieForValueType(oldItem.GetType().GetProperty(prop.Name), newItem.GetType().GetProperty(prop.Name), prop.Name));
				}
}
			return historien;
		}

		private IList<HistorienDTO> HistorieForClass<T>(T oldItem, T newItem) {
			var historien = new List<HistorienDTO>();
			HistorienCreater subCreater;
			foreach (PropertyInfo prop in oldItem.GetType().GetProperties()/*Hier gibt es einen StackOverflow*/) {
				CreateHistorie(oldItem.GetType().GetProperty(prop.Name), newItem.GetType().GetProperty(prop.Name));
				subCreater = new HistorienCreater();
				historien.AddRange(subCreater.CreateHistorie(oldItem.GetType().GetProperty(prop.Name), newItem.GetType().GetProperty(prop.Name)));
			}
			return historien;
		}

		private IList<HistorienDTO> HistorieForValueType<T>(T oldItem, T newItem, string propertyName) {
			var historien = new List<HistorienDTO>();
			string oldValue = oldItem.ToString();
			string newValue = newItem.ToString();
			if (!oldValue.Equals(newValue)) {
				historien.Add(new HistorienDTO(propertyName, oldValue, newValue));
			}
			return historien;
		}
	}
}

allerdings kriege ich jetzt einen stackoverflow, und das ganze an 2 stellen ( da wo es kommentiert ist). Das seltsame ist, er tritt mal an der einen und mal an der anderen stelle auf (bei den selben objekten). hat da evtl. jemand eine idee, woran das liegne könnte?

MfG
Craze

3.971 Beiträge seit 2006
vor 14 Jahren

CreateHistorie und HistorieForClass rufen sich gegenseitig auf, vermutlich hast du dort eine Endlosrekursion. Statt Rekursiv kannst du auch iterativ (Schleife) dein Objektbaum durchlaufen.

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

C
Craze89 Themenstarter:in
52 Beiträge seit 2008
vor 14 Jahren

Das die sich gegenseitig aufrufen ist auch gewollt ( auch wenn das zugegeben nicht so ganz fein ist ^^)

Endlos kann die rekursion nicht sein, aber wohl zu groß für den stack (aber den stack leeren und process weiter laufen is mir doch zu unsauber). Was mich allerdings wundert, ist das der fehler (oft) schon in der ersten ebene auftritt, in der (bei meinem momentanen TestObject) gerade mal 3 properties vorhanden sind.

natürlich könnte ich das ganze auch iterativ machen, aber das ist halt nicht ganz so kurz zum coden wie rekursiv 😄 (immer diese faulen informatiker XD)

aber probieren tu ich das trozdem mal, vielleicht wird das ja so doch etwas sauberer.

MfG
Craze

F
10.010 Beiträge seit 2004
vor 14 Jahren

Nur mal so, BO's die kein INotifyPropertyChanged, IEditableObject und IDataErrorInfo
implementieren sind für DataBinding sehr schlecht vorbereitet.

Wenn diese Interfaces fehlen ist beim design wohl etwas schief gegangen, und jemand
wusste nicht das es dieses gibt.

Diese Interfaces hätten dir viel arbeit erspart.

C
Craze89 Themenstarter:in
52 Beiträge seit 2008
vor 14 Jahren

@FZelle:

Der Kern deiner Aussage ist zwar korrekt, also das mir das arbeit ersparen würde,
aber wer arbeitet denn heutzutage bitte noch sofort auf BO's/DO's ?

bevor an irgendeinem datensatz rumgeschraubt wird, wird daraus ein DTO erstellt, an dem dann die änderungen vorgenommen werden, denn ein BO gehört definitiv nicht in einen View gesteckt, von dem aus es dann bearbeitet wird.

Da also die Daten von BO und DTO quasi "gemerged" werden, sind die von dir genannten interfaces für kaum einen anwendungsfall notwendig.

Aber schön wären sie in meinem aktuellen Fall dennoch, da muss ich dir recht geben 😃

MfG
Craze

//EDIT\

So ich Dokumentiere hier jetzt mal einfach noch n bissl meinen Fortschritt,
denn langsam kriegt das ganze Form 😄

	public class HistorienCreater
	{
		public IList<HistorienDTO> CreateHistorie<T>(T oldItem, T newItem) {
			var historien = new List<HistorienDTO>();
			foreach (PropertyInfo prop in oldItem.GetType().GetProperties()) {
				
				object oldObj = prop.GetValue(oldItem, BindingFlags.GetProperty, null, new object[0], CultureInfo.InvariantCulture);
				object newObj = prop.GetValue(newItem, BindingFlags.GetProperty, null, new object[0], CultureInfo.InvariantCulture);

				if (prop.PropertyType.IsValueType || prop.PropertyType.Name.Equals("String")) {
					
					historien.AddRange(HistorieForValueType(oldObj, newObj, prop.Name));
				}
				else if (prop.GetType().IsClass) {
					historien.AddRange(HistorieForClass(oldObj, newObj));
				}
			}
			return historien;
		}

		private IList<HistorienDTO> HistorieForClass<T>(T oldItem, T newItem) {
			var historien = new List<HistorienDTO>();
			HistorienCreater subCreater;
			foreach (PropertyInfo prop in oldItem.GetType().GetProperties()) {

				object oldObj = prop.GetValue(oldItem, BindingFlags.GetProperty, null, new object[0], CultureInfo.InvariantCulture);
				object newObj = prop.GetValue(newItem, BindingFlags.GetProperty, null, new object[0], CultureInfo.InvariantCulture);

				if (prop.GetType().IsValueType || prop.GetType().Equals(typeof(String))) {
					historien.AddRange(HistorieForValueType(oldObj, newObj, prop.Name));
				}
				else if (prop.GetType().IsClass) {
					subCreater = new HistorienCreater();
					historien.AddRange(subCreater.CreateHistorie(oldObj, newObj));
				}
			}
			return historien;
		}

		private IList<HistorienDTO> HistorieForValueType(object oldItem, object newItem, string propertyName) {
			var historien = new List<HistorienDTO>();
			string oldValue = oldItem.ToString();
			string newValue = newItem.ToString();
			if (!oldValue.Equals(newValue)) {
				historien.Add(new HistorienDTO(propertyName, oldValue, newValue));
			}
			return historien;
		}
	}
}

soweit so gut. Es gibt keinen StackOverflow mehr und ich krieg die richtigen properties. jetzt muss ich nur noch raus finden, warum Int32 != ValueType ist, oder hab ich mich da vertan und es ist tatsächlich keiner?

sobald ich dann alle Arten von Properties richti unterscheide, sollte das ganze endlich richtig funzen hoooray

MfG
Craze

PS: auch wenn ich langsam den weg finde, dürft ihr mir gerne weiterhin Anregungen geben 😃

//EDIT #2\

ich habs geschaft 😃

wenn man bei obigem Code das

if (prop.GetType().IsValueType || prop.GetType().Equals(typeof(String))) 

durch

if (prop.PropertyType.IsValueType || prop.PropertyType.Name.Equals("String"))

ersetzt erkennt er auch die ValueTypen und tut genau das, was er soll 😃

jetzt mach ich heute (oder montag) noch ein paar Feldstudien, um zu testen wie sich das ganze bei größeren objekte verhält, und wenn ich es nicht vergesse, werde ich dann bericht erstatten 😃

so far
Craze

F
10.010 Beiträge seit 2004
vor 14 Jahren

Nun deine Aussage ist nicht ganz richtig.

Wenn man mit MVVM arbeitet ist das sicherlich richtig, aber wenn man z.b.
mit ActiveRecord und MVP arbeitet, ist es durchaus normal die BO's direkt an den
View zu liefern.

C
Craze89 Themenstarter:in
52 Beiträge seit 2008
vor 14 Jahren

Ja ok, das es Grundsätzlich nicht so gemacht wird, war von mir vielleicht etwas doof ausgedrückt, da hast du recht, ist halt ne sache, die anscheinend überall unterschiedlich gehandhabt wird.

Also wir arbeiten nicht mit MVVM, aber mit MVP (aber kein ActiveRecord), und bei uns ist es, wie hier immer so schön gesagt wird "höchst illegal" eine (wie es bei usn heißt) DomainObject in den View zu geben.
Aber um ehrlich zu sein, hab ich bis jetzt auch noch von niemandem gehört, das er DO's / BO's in einen View gibt, aber da gibts sicherlich wie shcon gesagt unterschiedliche auffassungen.

Wenn ich heute dazu komme, werde ich das ganze nochmal gründlichst durchtesten (mit größeren Objekten) und dann bericht erstatten.

MfG
Craze

F
10.010 Beiträge seit 2004
vor 14 Jahren

Das ist das problem mit diesen ganzen Abkürzungen, sie werden meist nur so benutzt,
wie jemand sie erklärt bekommen hat, und das ist meist nicht wirklich richtig geschehen 😭

MVP wurde mal von Fowler in folgende 2 Möglichkeiten geteilt:
Supervising Controller und Passiv View

Bei ersterem wird ein Model ( auch das Domain Objekt ) an den View Übergeben,
und dort meist mit DataBinding an die UI Elemente gebunden.
Änderungen und arbeiten an dem Objekt werden dem Presenter mitgeteilt,
damit der reagieren kann.

Bei zweiterem werden die Werte direkt aus dem Model über öffentliche Properties des
Views gesetzt, so das die ganze arbeit dann der Presenter hat.

Beides benutzt ihr nicht.
Ihr benutzt DTO's die über einen Kompexen Presenter den View versorgt,
und das ist definitiv keine der o.g. Methoden, sondern entspricht eher MVVM.
Wobei eure DTO + dem Presenter eben dem ViewModel entsprechen.

C
Craze89 Themenstarter:in
52 Beiträge seit 2008
vor 14 Jahren

Naja dann haben wir so ne Mischung aus allem und unser Firmeneigenes DesignPattern 😄

hat ja auch was.

Aber ich muss dir leider schon wieder widersprechen (sry ^^)

im Passive View werden die Werte nicht DIREKT aus dem Model an den View gegeben

As a result, there is no dependencies in either direction between the view and the model. Aus der gelinkten seite 😉

Und ich würde auch Model und Domain Object NICHT auf eine Stufe stellen (und wenn man das doch tut, ist es noch ei ngrund mehr es nicht in dne View zu geben 😉)

Des weitere sollte man DTO's benutzen, gerade wenn man DataBinding benutzt (ok das ist meine ansicht, und die teilt definitiv nicht jeder), denn ICH möchte nicht, das jedes gefrickel von einem User im View ungefiltert in mein DomainObject gelangen KÖNNTE (könnte, weil ich das weis, wenn ich mein eigenes DO benutze). Denn wenn jemand anders das DO benutzt, weis ich nicht wie und ob er die Daten filtert, bevor er sie ins DomainObject schreibt. bei einem DTO bleibt einem quasi nichts andres mehr übrig, denn man muss die Daten zumindest "mergen" und spätestens dann fällt einem halbwegs gescheiten Coder auf, das man die Werte evtl. erstmal validieren sollte.
Hat dann den Vortiel, das die Daten ersma alle ohne rücksicht auf verluste ins DTO "gehämmert" werden, und dann in aller ruhe validiert und ggf. gespeichert werden können.

Aber eigentlich gehört das alle hier auch garnicht hin 😄, denn mein HistorienCreater hat an sich nichts mit MVP oder MVVM zu tun, sondern wird später mal teil eines Models und mit der validierung dürfen sich dann andre rumschlagen 😃

//EDIT\

So die spezifikationen haben sich gerade "etwas" geändert und mir mein leben um einiges leichter gemacht.

die verschachtelungen sind mir jetzt egal, aber wenn das Property kein ValueType oder String ist, wird ein eintrag gemacht, der einfach nur sagt das sich das property geändert hat. Da dürfen dan ndie andrne programmierer selber entscheiden, ob sie noch eine ebene Tiefer gehn, in dem sie die "unterobjekte" in den HistorienCreater schmeißen oder nicht.

Das ganze sieht jetzt wie folgt aus:

	public class HistorienCreater
	{
		public static IList<HistorienDTO> CreateHistorie<T>(T oldItem, T newItem) {
			var historien = new List<HistorienDTO>();

			object objectToIterrate = oldItem != null ? oldItem : newItem;
			if (objectToIterrate == null) {
				return new List<HistorienDTO>();
			}

			foreach (PropertyInfo prop in objectToIterrate.GetType().GetProperties()) {
				object oldObj;
				object newObj;
				try {
					oldObj = oldItem != null ? prop.GetValue(oldItem, BindingFlags.GetProperty, null, new object[0], CultureInfo.InvariantCulture) : null;
					newObj = newItem != null ? prop.GetValue(newItem, BindingFlags.GetProperty, null, new object[0], CultureInfo.InvariantCulture) : null;
				}
				catch (TargetParameterCountException) {
					continue;
				}

				if (prop.IsValueTypeOrString() || (objectToIterrate.GetType().GetProperties().Length == 1 && prop.IsValueTypeOrString())) {
					historien.AddRange(HistorieForValueType(oldObj, newObj, prop.Name));
				}
				else if (prop.GetType().IsClass) {
					if (!oldObj.Equals(newObj)) {
						historien.Add(new HistorienDTO(prop.Name, "Property changed", ""));
					}
				}
			}
			return historien;
		}

		private static IList<HistorienDTO> HistorieForValueType(object oldItem, object newItem, string propertyName) {
			var historien = new List<HistorienDTO>();
			string oldValue = oldItem == null ? "" : oldItem.ToString();
			string newValue = newItem == null ? "" : newItem.ToString();
			if (!oldValue.Equals(newValue)) {
				historien.Add(new HistorienDTO(propertyName, oldValue, newValue));
			}
			return historien;
		}
	}

und bis jetzt scheints korrekt zu funktionieren

MfG
Craze

F
10.010 Beiträge seit 2004
vor 14 Jahren

Da hast du dann meine Aussage vom PassivView falsch aufgefasst.

Ich meinte, das der Wert in den View gegeben wird, nicht eine Referenz auf
das Property des Models.
Deshalb ist dann der Presenter zuständig dafür die Daten ggf wieder zurückzuholen
und im Model neu zu setzen.

Und auch dein Validierungsproblem ist eigentlich keines, denn über
INotifyPropertyChanging kann man validierung einbauen, die dann per IDataErrorInfo
gelesen werdern kann.
IEditableObject kann dann ein Rücksetzen der Änderungen erledigen.

Da alle FrameworkControls diese Mechanismen unterstützen, ist dann
auch DataBinding sehr einfach.

Insgesamt ist das dann KISS und YAGNI in einem.
Aber wenn ihr es lieber komplexer haben wollt 😉

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo ihr beiden,

so interessant eure Diskussionen zu den GUI-Pattern sind, sie passenden leider weder in den Thread noch in das Unterforum, weshalb ich sie zumindest mal ausgegraut habe.

herbivore