如何在运行时不知道类型的情况下创建Expression.Lambda?

18

最好通过代码来解释。我有一个通用类,其中有一个返回整数的方法。为了说明,这里是一个简单版本...

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

在运行时,我使用反射来发现某个东西的类型,然后想要为该特定类型创建我的Gen类的实例。这很容易,可以像这样完成...

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

我现在想创建一个表达式,它将以泛型类型实例作为参数,然后调用该类型的DoSomething方法。因此,我希望表达式有效地执行以下操作...

int answer = genericInstance.DoSomething(instance);

除了我在运行时某个时间点才有“实例(instance)”,而泛型实例(genericInstance)是如上所示的生成类型。我尝试创建Lambda表达式的方式如下...
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();

...这样以后我就可以像这样调用它...

int answer = x(genericInstance, instance);

当然,您无法为 Func 提供实例参数,因此我不知道如何对 Lambda 生成进行参数化。有什么想法吗?
3个回答

26

我认为你只需要使用以委托类型作为类型而不是泛型的Expression.Lambda,并像你使用Gen<>一样即时创建你的Func:

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();

这将返回一个委托而不是强类型的Func,但如果需要(并且如果您不知道要将其转换为什么,则似乎很困难),您当然可以将其强制转换,或者在其上动态调用DynamicInvoke

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

编辑:

这确实是一个好主意并且有效。但不幸的是,我想使用强类型编译的Lambda是出于性能的原因。与类型化的Lambda相比,使用DynamicInvoke非常慢。

这似乎可以在不需要动态调用的情况下工作。

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()();

编辑 2:

一个大大简化的版本:

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()();

一个确实可行的好主意。不幸的是,我想使用强类型编译的Lambda的原因是性能。与类型化的Lambda相比,使用DynamicInvoke要慢得多。 - Phil Wright
能否在表达式树中捕获变量?对于永远不会改变的genericInstance,这将有所帮助。 - Phil Wright
@PhilWright 嗯,我明白了。让我看看我还能想出什么。 - vcsjones
@PhilWright 编辑过了。试一下吧。你可能可以简化一下;但我认为这是正确的方向。 - vcsjones
非常准确,谢谢。我刚试了一下,它运行得非常好,速度也很快。 - Phil Wright

2

如果您使用的是.NET 4.0,那么这个答案才适用。

如果您将genericInstance改为dynamic而不是object,那么您可以直接调用它的DoSomething方法,动态语言运行时会自动处理一切。

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
}

如果您需要保留这个类结构(Gen<T>),那么我认为在编译时无法知道类型T,如果您想调用委托,您必须要在编译时知道其完整类型,或者将参数作为对象传递。
使用dynamic可以隐藏获取MethodInfo等的复杂性,并具有出色的性能。 我认为与DynamicInvoke相比唯一的缺点是,如果您在每个调用站点上都调用它,则会获得解析动态调用的初始开销。 绑定被缓存,因此如果您对具有相同类型的对象进行调用,则从第二次开始运行非常快。

我以前从未听说过“动态”这个词,但它刚刚为我节省了很多时间。干杯! - RagedMilkMan

1

最好接受一个对象并使用convert转换为已知类型。

以下是一个示例,演示如何在未知深度上按名称构建属性访问:

var model = new { A = new { B = 10L } };
string prop = "A.B";
var parameter = Expression.Parameter(typeof(object));
Func<object, long> expr = (Func<object, long>) Expression.Lambda(prop.Split('.').Aggregate<string, Expression>(Expression.Convert(parameter, model.GetType()), Expression.Property), parameter).Compile();
expr(model).Dump();

在编译时未知委托类型时,它避免了使用DynamicInvoke产生的额外成本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接