Laden...

[gelöst] Expression.Lamda mit Interface Type

Erstellt von Mossi vor 10 Jahren Letzter Beitrag vor 10 Jahren 1.703 Views
Mossi Themenstarter:in
199 Beiträge seit 2006
vor 10 Jahren
[gelöst] Expression.Lamda mit Interface Type

Der Titel ist glaub ich jetzt nicht wirklich aussagekräftig, aber ich hab keine Ahnung, wie ich den Titel benennen soll. Ich hoffe, dass ich durch den folgenden Text meine Frage sauber formulieren kann, so dass klar ist, was ich letztendlich erreichen will.

Ich hab eine Methode, die mir einen Lamda-Expression zusammensetzt und compiliert zurück gibt. Das Ganze sieht so aus:


public static Func<DomainObject1, bool> BuildFilter(DomainObject1 obj, IList<ILookup> lookups)
{
	ParameterExpression param = Expression.Parameter(obj.GetType(), "t");
	Expression exp = null;

	if (lookups == null || lookups.Count == 0)
	{
		ConstantExpression constant = Expression.Constant(true);
		exp = Expression.IsTrue(constant);
	}
	else if (lookups.Count == 1)
                exp = GetFilterExpression(obj.GetType(), param, lookups[0]);
	...

	return Expression.Lambda<Func<DomainObject1, bool>>(exp, param).Compile();
}

Das Ganze funktioniert wunderbar. Jetzt ist es aber so, dass DomainObject1 zusammen mit vielen anderen Objekten vom Interface _IDomainObject _ableitet. Natürlich soll die Methode mit allen Objekten verwendet werden können, die von IDomainObject ableiten. Die Signatur der Methode würde also folgendermaßen aussehen:

public static Func<IDomainObject, bool> BuildFilter(IDomainObject obj, IList<ILookup> lookups)

Jetzt hab ich aber das Problem, dass ich im letzten Statement eine ArgumentException bekomme:> Fehlermeldung:

Der ParameterExpression vom Typ "DomainObject1" kann nicht für Delegatparameter vom Typ "IDomainObject" verwendet werden.

Grund ist der, dass ein ParameterExpression vom Type _DomainObject1 _erzeugt wird.

Mein nächster Versuch war dann, dass ich den ParameterExpression eben mit einem anderen Type erzeuge:

ParameterExpression param = Expression.Parameter(typeof(IDomainObject). "t");

Dann funktioniert der Aufruf von GetFilterExpression nicht mehr. Auch kein Problem - hab ich mir gedacht, dass passe ich den eben auch noch an. Dann kommen wir in die nächste Methode:


private static Expression GetFilterExpression(Type realtype, ParameterExpression param, ILookup filter)
{
	MemberExpression member = Expression.Property(param, realtype.GetPropertyExt(filter.Column));
	ConstantExpression constant = Expression.Constant(filter.Value);
        ...

Hier hab ich jetzt das Problem, dass als realtype natürlich nur noch IDomainObject übergeben wird. In diesem Interface gibt es die die angegebene Property (aus filter.Column) nicht und es kracht wieder.

Ich weiß gerade echt nicht mehr, wie ich zum gewünschten Ergebnis kommen könnte. In meinen Augen müsste ich den Rückgabewert der Methode BuildFilter irgendwie dynamisch abändern. Aktuell gebe ich hier ein Objekt vom Type Func<DomainObject1, bool> zurück. Stattdessen bräuchte ich irgendwas in der Art: Func<var, bool>
Aber ich wüsste nicht, dass das möglich ist.

Vielleicht hat jemand einen Ansatz für mich, wie ich da weitermachen könnte.
Danke schon mal

S
417 Beiträge seit 2008
vor 10 Jahren

Hi,

schonmal versucht das Ganze generisch zu lösen? z.B.:

public static Func<T, bool> BuildFilter<T>(T obj, IList<ILookup> lookups) where T : IDomainObject
{
...
    return Expression.Lambda<Func<T, bool>>(exp, param).Compile();
}
Mossi Themenstarter:in
199 Beiträge seit 2006
vor 10 Jahren

Da das Ganze schon sehr generisch abläuft, ist das glaub ich nicht ganz einfach. Als erstes muss dabei erwähnt werden, dass der Name des Objekt Types nur als String bekannt ist. Aber das ist erst einmal nicht das Problem. Aber ich denke, dass man so vielleicht besser auf eine mögliche Lösung kommen könnte:

Die Neue Signatur der Methode sieht so aus:

public Func<T, bool> BuildFilter<T>(T obj, IList<ILookup> lookups) where T : IDomainObject

Ich geh nochmal einen Schritt zurück und zweige den Aufruf auch noch:


public function FetchDataFromTable(string tablename, IList<ILookup> lookups)
{
    IList<IDomainObject> data = new List<IDomainObject>();
    IDomainObject table = TableFactory.GetTable(tablename);
    ...
    var selectedData = data.Where(ExpressionBuilder.Instance.BuildFilter(table, lookups));
}

Das Where erwartet einen echtes Object vom Type Func<IDomainObject, bool>

Ich hab mal versucht einen Wrapper aussenrum zu basteln


public Func<IDomainObject, bool> Test(IDomainObject obj, IList<ILookup> lookups)
{
	MethodInfo genericMethod = this.GetType().GetMethod("BuildFilter");
	MethodInfo finalMethod = genericMethod.MakeGenericMethod(obj.GetType());
	return (Func<IDomainObject, bool>)finalMethod.Invoke(this, new object[] { obj, lookups });
}

Aber jetzt hab ich wieder in der letzten Zeile des Wrappers das Problem, dass ich nicht korrekt konvertieren kann (siehe Fehlermeldung oben). Einfach object zurückgeben funktioniert leider auch nicht, weil falscher Type.

H
114 Beiträge seit 2007
vor 10 Jahren

Hallo Mossi,

wenn ich das richtig verstehe, willst du mit dem erstellten Delegaten vom Typ Func<IDomainObject, bool> eine Liste List<IDomainObject> filtern, in der nur Objekte genau eines Types enthalten sind, den du als String bekommst? Soweit richtig?

Wenn ja, dann könnte Expression.Convert eine Lösung für dich sein. Damit kannst du deinen Parameter vom Typ IDomainObject beim Zugriff jedesmal in den konkreten Typen casten.
Aber Achtung...Diese Lösung erfordert es, dass die Typen dann zur Laufzeit eben auch passen! Eine Liste von IDomainObject-Objekten kann ja theoretisch auch aus DomainObject1 und DomainObject2-Objekten bestehen und dann geht es zur Laufzeit kaputt 😉

Kurz und knapp...den Code aus dem ersten Post


private static Expression GetFilterExpression(Type realtype, ParameterExpression param, ILookup filter)
{
    MemberExpression member = Expression.Property(param, realtype.GetPropertyExt(filter.Column));
    ConstantExpression constant = Expression.Constant(filter.Value);
	...

könntest du ersetzen mit...


private static Expression GetFilterExpression(Type realtype, ParameterExpression param, ILookup filter)
{
    MemberExpression member = Expression.Property(
		Expression.Convert(param, realtype), 
		realtype.GetPropertyExt(filter.Column));
    ConstantExpression constant = Expression.Constant(filter.Value);
	...

param repräsentiert eine ParameterExpression vom Typ IDomainObject, während realtyp den echten .NET-Laufzeittyp (z.B. DomainObject1) repräsentiert.
Hoffe ist soweit verständlich, Code habe ich nicht getestet sondern hier im Editor runter getippt...Also kein Gewähr auf Fehlerfreiheit 😉

Grüße, HiGHteK

Mossi Themenstarter:in
199 Beiträge seit 2006
vor 10 Jahren

Perfekt... das war die Lösung.
Ich muss zwar den Code noch ein bisschen anpassen, weil ich das gleiche Dilemma auch noch an anderen Stellen habe, wo es wieder ein bisschen anders läuft, aber die Lösung naht 😃

Vielen Dank

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Mossi,

Eine Liste von IDomainObject-Objekten kann ja theoretisch auch aus DomainObject1 und DomainObject2-Objekten

und das ist auch der Grund, warum du die Fehlermeldung bekommen hast. Für einen einen Delegatentyp, der einen IDomainObject als Parameter erwartet, kann kein konkreter Delegat verwendet werden, der das Parameter nur DomainObject1 akzeptiert, weil der Compiler davon ausgehen muss, dass als Parameter auch z.B. DomainObject2 übergeben werden könnte.

IDomainObject x = new DomainObject1 (...); // geht
Func<IDomainObject, ...> y = new Func <DomainObject1, ...> (...); // geht deshalb aber noch lange nicht

Und das gilt nicht nur für Func, sondern jeden beliebigen generischen Typ, z.B. auch IList<>, wo man es sich bei der Add-Methode besonders einfach überlegen kann.

herbivore

2.079 Beiträge seit 2012
vor 10 Jahren

@Thread-Starter:

Ein Tipp, der dir vielleicht hilft, sind Variante Typparameter.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.