如果直到运行时才知道类型,如何创建Expression.Lambda?

最好使用代码解释。 我有一个generics类,它有一个返回整数的方法。 这是一个简单的版本,用于解释…

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

在运行时,我使用reflection来发现某事物的类型,然后想要为该特定类型创建我的Gen类的实例。 这很容易,像这样做……

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

我现在想要创建一个Expression,它将参数作为generics类型的一个实例,然后调用该类型的DoSomething方法。 所以我希望Expression能有效地执行此操作……

 int answer = genericInstance.DoSomething(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> (Expression.Call(p1, mi, p2), new[] { p1, p2 }).Compile(); 

…所以以后我可以用这样的东西来称它…

 int answer = x(genericInstance, instance); 

当然,您无法为Func提供实例参数,因此我不知道如何参数化Lambda生成。 有任何想法吗?

我想你只会使用Expression.Lambda ,它将委托类型作为一个类型而不是generics,并像使用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(); 

这将返回一个Delegate而不是一个强类型的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>(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>(Expression.Call(Expression.Constant(genericInstance), mi, value)); var answer = lambda.Compile()(); 

此答案仅适用于使用.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的简单方法。 如果要调用委托,则必须在编译时知道其完整类型,或者需要将参数作为对象传递。

使用dynamic可以隐藏获取MethodInfo等的复杂性,并为您提供出色的性能。 我看到的与DynamicInvoke相比的一个缺点是,我相信你得到了为每个呼叫站点解析一次动态调用的初始开销。 如果在具有相同类型的对象上调用绑定,则会缓存绑定,以便它们从第二次开始运行得非常快。

最好接受一个object并使用convert为已知类型。

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

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

当编译时未知委托类型时,它可以避免额外的DynamicInvoke成本。