如果直到运行时才知道类型,如何创建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
当编译时未知委托类型时,它可以避免额外的DynamicInvoke成本。