繁体   English   中英

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

[英]How to create a Expression.Lambda when a type is not known until runtime?

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

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

我现在想要创建一个Expression,它将参数作为泛型类型的一个实例,然后调用该类型的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<genericType, fieldType, int>>
            (Expression.Call(p1, mi, p2), 
             new[] { p1, p2 }).Compile();

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

int answer = x(genericInstance, instance);

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

我想你会使用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();

这将返回一个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<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()();

此答案仅适用于使用.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相比的一个缺点是,我相信你会得到为每个呼叫站点解析一次动态调用的初始开销。 如果在具有相同类型的对象上调用绑定,则会缓存绑定,以便它们从第二次开始运行得非常快。

最好接受一个object并使用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成本。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM