【问题标题】:How to create a Expression.Lambda when a type is not known until runtime?如何在运行时才知道类型时创建 Expression.Lambda?
【发布时间】:2011-12-09 17:05:49
【问题描述】:

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

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

...除了我在运行时的某个时间点之前没有“实例”,而 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 生成。有什么想法吗?

【问题讨论】:

    标签: c# generics lambda expression-trees


    【解决方案1】:

    我认为您只需使用将委托类型作为类型而不是泛型的 Expression.Lambda,并像使用 Gen&lt;&gt; 一样动态创建您的 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,但您当然可以根据需要进行转换(如果您不知道要转换到什么,这似乎很困难),或者使用 @987654325 动态调用它@就可以了。

    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 相当慢。
    • 是否可以在表达式树中捕获变量?我会帮助捕获 genericInstance,因为它永远不会改变。
    • @PhilWright 嗯,我明白了。让我看看我还能想出什么。
    • @PhilWright 已编辑。试一试。您可以稍微简化一下;但我认为这是正确的道路。
    • 正确,谢谢。我刚试过这个,效果很好,速度也很快。
    【解决方案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&lt;T&gt;),那么我看不出一个简单的方法来解决你在编译时不知道类型 T 的事实。如果要调用委托,要么必须在编译时知道其完整类型,要么需要将参数作为对象传入。

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

    【讨论】:

    • 我从来不知道动态这个词,但它为我节省了很多时间。干杯!
    【解决方案3】:

    最好接受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 的额外成本。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-19
      • 1970-01-01
      • 2011-08-18
      • 1970-01-01
      • 1970-01-01
      • 2012-09-19
      • 1970-01-01
      相关资源
      最近更新 更多