Wie erstelle ich ein Expression.Lambda, wenn ein Typ bis zur Laufzeit nicht bekannt ist?

c# expression-trees generics lambda

Frage

Dies wird am besten mit Code erklärt. Ich habe eine generische Klasse mit einer Methode, die eine ganze Zahl zurückgibt. Hier ist eine einfache Version zum Zweck der Erklärung ...

public class Gen<T>
{
    public int DoSomething(T instance)
    {
        // Real code does something more interesting!
        return 1;
    }
}

Zur Laufzeit verwende ich Reflektion, um den Typ von etwas zu entdecken und dann eine Instanz meiner Gen-Klasse für diesen speziellen Typ zu erstellen. Das ist einfach genug und so gemacht ...

Type fieldType = // This is the type I have discovered
Type genericType = typeof(Gen<>).MakeGenericType(fieldType);
object genericInstance = Activator.CreateInstance(genericType);

Ich möchte nun einen Ausdruck erstellen, der eine Instanz des generischen Typs als Parameter akzeptiert und anschließend die DoSomething-Methode dieses Typs aufruft. Ich möchte, dass der Ausdruck das effektiv ausführt ...

int answer = genericInstance.DoSomething(instance);

... außer dass ich die 'Instanz' erst einen Punkt später zur Laufzeit habe und die generischeInstanz der generierte Typ ist, wie oben zu sehen ist. Mein Versuch, das Lambda dafür zu erstellen, ist wie folgt ...

MethodInfo mi = genericType.GetMethod("DoSomething", 
                                      BindingFlags.Instance | BindingFlags.Public);

var p1 = Expression.Parameter(genericType, "generic");
var p2 = Expression.Parameter(fieldType, "instance");

var x = Expression.Lambda<Func<genericType, fieldType, int>>
            (Expression.Call(p1, mi, p2), 
             new[] { p1, p2 }).Compile();

... damit ich es später so nennen kann ...

int answer = x(genericInstance, instance);

Natürlich können Sie Func nicht mit Instanzparametern versorgen und daher habe ich keine Ahnung, wie die Lambda-Generation parametrisiert werden soll. Irgendwelche Ideen?

Akzeptierte Antwort

Ich denke, dass Sie nur die Expression.Lambda , die den Delegattyp als Typ und nicht als generischen Typ verwendet und Ihren Func im laufenden Betrieb wie mit Gen<> :

MethodInfo mi = genericType.GetMethod("DoSomething",
                                BindingFlags.Instance | BindingFlags.Public);

var p1 = Expression.Parameter(genericType, "generic");
var p2 = Expression.Parameter(fieldType, "instance");
var func = typeof (Func<,,>);
var genericFunc = func.MakeGenericType(genericType, fieldType, typeof(int));
var x = Expression.Lambda(genericFunc, Expression.Call(p1, mi, p2),
                new[] { p1, p2 }).Compile();

Dies wird einen Delegierten anstelle eines stark typisierten Func , aber Sie können ihn natürlich bei Bedarf Func (und scheinbar schwierig, wenn Sie nicht wissen, woran Sie DynamicInvoke ) oder ihn dynamisch mit DynamicInvoke aufrufen.

int answer = (int) x.DynamicInvoke(genericInstance, instance);

EDIT :

Eine gute Idee, die tatsächlich funktioniert. Leider ist der Grund, warum ich ein stark typisiertes kompiliertes Lambda verwenden möchte, die Performance. Die Verwendung von DynamicInvoke ist im Vergleich zu einem typisierten Lambda sehr langsam.

Dies scheint ohne die Notwendigkeit eines dynamischen Aufrufs zu funktionieren.

var p1 = Expression.Parameter(genericType, "generic");
var p2 = Expression.Parameter(fieldType, "instance");
var func = typeof(Func<,,>);
var genericFunc = func.MakeGenericType(genericType, fieldType, typeof(int));
var x = Expression.Lambda(genericFunc, Expression.Call(p1, mi, p2), new[] { p1, p2 });
var invoke = Expression.Invoke(x, Expression.Constant(genericInstance), Expression.Constant(instance));
var answer = Expression.Lambda<Func<int>>(invoke).Compile()();

EDIT 2 :

Eine stark vereinfachte Version:

Type fieldType = ;// This is the type I have discovered
Type genericType = typeof(Gen<>).MakeGenericType(fieldType);
object genericInstance = Activator.CreateInstance(genericType);
MethodInfo mi = genericType.GetMethod("DoSomething",
                                BindingFlags.Instance | BindingFlags.Public);
var value = Expression.Constant(instance, fieldType);
var lambda = Expression.Lambda<Func<int>>(Expression.Call(Expression.Constant(genericInstance), mi, value));
var answer = lambda.Compile()();

Beliebte Antwort

Diese Antwort gilt nur, wenn Sie .NET 4.0 verwenden.

Wenn Sie genericInstance anstelle von object dynamic machen, können Sie die DoSomething Methode direkt aufrufen, und die dynamische Sprachlaufzeit kümmert sich um alles für Sie.

class Type1 {
    public int DoSomething() { return 1; }
}
class Type2 {
    public int DoSomething() { return 2; }
}

static void TestDynamic() {
    dynamic t1 = Activator.CreateInstance(typeof(Type1));
    int answer1 = t1.DoSomething(); // returns 1

    dynamic t2 = Activator.CreateInstance(typeof(Type2));
    int answer2 = t2.DoSomething(); // returns 2
}

Wenn Sie diese Klassenstruktur behalten müssen ( Gen<T> ), dann sehe ich keinen einfachen Weg um die Tatsache, dass Sie den Typ T zur Kompilierzeit nicht kennen. Wenn Sie den Delegaten aufrufen möchten, müssen Sie entweder den vollständigen Typ zum Zeitpunkt der Kompilierung kennen, oder Sie müssen die Parameter als Objekte übergeben.

Mit dynamic Sie die Komplexität von MethodInfo usw. ausblenden und erhalten eine hervorragende Leistung. Der einzige Nachteil gegenüber DynamicInvoke , den ich sehe, ist, dass ich glaube, dass Sie den anfänglichen Overhead erhalten, den dynamischen Aufruf einmal für jeden Aufrufstandort zu lösen. Die Bindungen werden zwischengespeichert, so dass sie ab dem zweiten Mal sehr schnell laufen, wenn Sie sie für Objekte desselben Typs aufrufen.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum