表达式树和调用委托

Posted

技术标签:

【中文标题】表达式树和调用委托【英文标题】:Expression Trees and Invoking a Delegate 【发布时间】:2011-01-14 00:22:19 【问题描述】:

所以我有一个delegate,它指向一些我在第一次创建delegate 对象时实际上并不知道的函数。该对象稍后会设置为某个函数。

然后我还想创建一个表达式树,它使用参数调用委托(为了这个问题,参数可以是5)。这是我正在努力解决的问题;下面的代码显示了我想要的,但它没有编译。

Func<int, int> func = null;
Expression expr = Expression.Invoke(func, Expression.Constant(5));

对于这个例子我可以做(这很实用,因为我需要在运行时构建表达式树):

Func<int, int> func = null;
Expression<Func<int>> expr = () => func(5);

这使得expr 变成:

() => Invoke(value(Test.Program+<>c__DisplayClass0).func, 5)

这似乎意味着要使用delegate func,我需要生成value(Test.Program+&lt;&gt;c__DisplayClass0).func 位。

那么,如何创建一个调用委托的表达式树?

【问题讨论】:

Linq in Action 有一个关于表达式树的详细部分。 【参考方案1】:

我认为你想要做的是使用委托的 Target 和 Method 属性来传递来创建一个 Call 表达式。以 JulianR 的样本为基础,它看起来是这样的:

Action<int> func = i => Console.WriteLine(i * i);

var callExpr = Expression.Call(Expression.Constant(func.Target), func.Method, Expression.Constant(5));

var lambdaExpr = Expression.Lambda<Action>(callExpr);
var fn = lambdaExpr.Compile();
fn();    //  Prints 25

【讨论】:

我必须用 null 替换 Expression.Constant(func.Target) 才能让它工作。但这与函数 func 当前指向的内容绑定,而不是稍后可能指向的内容。问题是无法对 func 指向的内容做出任何假设,并且它可能随时更改。 这几乎是我需要的答案,但请从 Expression.Call 中删除第一个参数。否则会出现异常(我在 .NET4 和 .NET4.5 上都检查过) @ironic:你在测试时目标方法是静态方法吗?这可能就是区别。 目标实际上是您的示例中的一个:i => Console.WriteLine(i * i); ,它是静态的,因为它不引用任何东西。【参考方案2】:

好的,这显示了它是如何可以完成的(但在我看来它非常不雅):

Func<int, int> func = null;
Expression<Func<int, int>> bind = (x) => func(x);

Expression expr = Expression.Invoke(bind, Expression.Constant(5));

Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(expr);
Func<int> compiled = lambda.Compile();

Console.WriteLine(expr);

func = x => 3 * x;
Console.WriteLine(compiled());

func = x => 7 * x;
Console.WriteLine(compiled());

Console.Read();

基本上我使用(x) =&gt; func(x); 来创建一个调用委托所指向的函数。但是您可以看到expr 过于复杂。出于这个原因,我不认为这个答案很好,但也许可以建立在它的基础上?

【讨论】:

您创建的表达式需要有一种方法可以通过引用访问 func 变量。您不能创建对局部变量的引用。在您的代码中,您使用绑定 lambda 来捕获局部变量,而 C# 编译器会发挥它的魔力并创建一个单独的类来保存看起来像局部变量的东西。您可以自己执行此操作,而不必使用绑定表达式,但生成的 lambda 可能同样复杂。 我很想看看“自己动手做的方法”。我想这需要 Reflection.Emit 的一些魔法?【参考方案3】:

虽然其他答案提供了一些工作方式,但还有一种更短的方式:

Expression.Invoke(Expression.Constant(my_delegate), parameter_for_delegate)

它既适用于引用静态方法的委托,也适用于没有变化的实例方法。

【讨论】:

虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。 @Nic3500 我绝对同意,但我觉得不是扩展它的合适人选。我只是注意到有很多答案在某种程度上有效,但并不普遍有效。投票最多的答案不适用于引用静态方法 iirc 的代表。接受的答案太复杂了。我将答案更改为“社区 wiki”以允许有人对其进行扩展。【参考方案4】:

这应该可行:

Action<int> func = i => Console.WriteLine(i * i);

// If func is null like in your example, the GetType() call fails, 
// so give it a body or use typeof if you know the type at compile time
var param = Expression.Parameter(func.GetType());

// Call the Invoke method on the delegate, which is the same as invoking() it
var callExpr = Expression.Call(param, func.GetType().GetMethod("Invoke"), Expression.Constant(5)); 

var lambdaExpr = Expression.Lambda<Action<Action<int>>>(callExpr, param); 

var fn = lambdaExpr.Compile(); // Compile the expression tree so it can be executed 

fn(func); // Prints 25

表达式可能很麻烦,但请记住:表达式总是由其他表达式构建而成的。表达式是描述代码的其他表达式的树。您不能像在示例中那样传递实际的委托,您需要的是该委托的表达式,通过说表达式需要委托类型的参数。然后你说你想在那个参数上调用一个方法,即Invoke 方法,参数为'5'。之后的所有其他事情只是如果您想将表达式转换为可运行的代码,您可能会这样做。

虽然我用 .NET4 运行了这个,但我希望我没有混入 .NET4 唯一的表达式内容。

编辑回应 PythonPower 的评论:

我认为你想要的(不作为参数传入委托)只有在委托本身被描述为表达式时才能完成,如下所示:

 var arg = Expression.Parameter(typeof(int), "i");

 var multiply = Expression.Multiply(arg, arg);

 var writeln = Expression.Call(typeof(Console).GetMethod("WriteLine", 
   new[]  typeof(int) ), multiply);

 var lambda = Expression.Lambda<Action<int>>(writeln, arg);

 var compiled = lambda.Compile();

 compiled(5); // Prints 25

我能想到的唯一其他方法是捕获在闭包中本地声明的委托,但我不知道该怎么做。

【讨论】:

这非常接近我想要的,但我不想将委托作为参数传递。 编辑假定我知道函数,但直到创建表达式后我才知道函数。并且函数本身不一定是表达式。关闭的想法听起来很有希望。

以上是关于表达式树和调用委托的主要内容,如果未能解决你的问题,请参考以下文章

表达式树与委托

(VIP-朝夕教育)2021-05-28 .NET高级班 13-表达式目录树的使用

初识委托,泛型委托,表达式树,Lanbda

C#委托,匿名方法,Lambda,泛型委托,表达式树代码示例

将委托持久化及利用表达式树从持久化库还原委托

将委托持久化及利用表达式树从持久化库还原委托