表达式树中的 C# 4“动态”

Posted

技术标签:

【中文标题】表达式树中的 C# 4“动态”【英文标题】:C# 4 "dynamic" in expression trees 【发布时间】:2011-04-03 11:44:17 【问题描述】:

我正在尝试弄清楚如何将所有部分组合在一起,并希望有一个具体的源代码示例作为一个简单的案例开始。

考虑以下 C# 代码:

Func<int, int, int> f = (x, y) => x + y;

我可以在运行时使用表达式树生成一个等效函数,如下所示:

var x = Expression.Parameter(typeof(int), "x");
var y = Expression.Parameter(typeof(int), "y");
Func<int, int, int> f =
    Expression.Lambda<Func<int, int, int>>(
        Expression.Add(x, y),
        new[]  x, y 
    ).Compile();

现在给出以下 lambda:

Func<dynamic, dynamic, dynamic> f = (x, y) => x + y;

如何使用表达式树(可能还有Expression.Dynamic)生成等效项?

【问题讨论】:

【参考方案1】:

您可以通过将动态 C# 加法表达式的 CallSiteBinder 传递到 Expression.Dynamic 来创建表示动态 C# 加法表达式的表达式树。您可以通过在原始动态表达式上运行 Reflector 来发现创建 Binder 的代码。你的例子会是这样的:

var x = Expression.Parameter(typeof(object), "x");
var y = Expression.Parameter(typeof(object), "y");
var binder = Binder.BinaryOperation(
    CSharpBinderFlags.None, ExpressionType.Add, typeof(Program),
    new CSharpArgumentInfo[]  
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null));
Func<dynamic, dynamic, dynamic> f =
    Expression.Lambda<Func<object, object, object>>(
        Expression.Dynamic(binder, typeof(object), x, y),
        new[]  x, y 
    ).Compile();

【讨论】:

酷,很有趣。不确定您是否回答了 OP 的问题,但您肯定会按照他希望的方式解决问题。 太好了,这正是我想要的,谢谢!从 MSDN 文档中查看 Binder.BinaryOperation (msdn.microsoft.com/en-us/library/ee814532.aspx) 时我还不太清楚的另一件事 - “上下文”参数的含义是什么? C# 是否总是在那里使用封闭类型的名称?除了将特定呼叫站点标记为与所有其他呼叫站点不同(我假设出于缓存目的)之外,它是否具有任何特殊的语义含义?例如,如果我在同一个类中有两个方法可能会以不同的方式分派相同的东西,我是否要创建两个虚拟类? @Pavel:我认为它用于确定可以访问哪些成员。例如,如果您有private void Foo(string s) public void Foo(object o) ,则在类内部对字符串调用 Foo 将选择字符串重载,但在类外部将选择对象重载。 @Pavel:不客气!顺便说一句,接受答案不会奖励赏金,所以它仍然是开放的。你还有什么想要的吗? @Quartermeister:对不起,我不怎么使用赏金功能,所以我错过了它不是自动的事实。我对此没有任何进一步的问题(现在,无论如何),就这样吧。【参考方案2】:

您不能这样做,因为表达式树“可能不包含动态操作”。

例如,由于 + 操作,以下内容将无法编译,并且您正在尝试构建违反该规则的表达式树:

 Expression<Func<dynamic, dynamic, dynamic>> f = (x, y) => x + y;

如果您不执行 Add 操作,您可以侥幸成功。

更多信息请参见How to create an Expression<Func<dynamic, dynamic>> - Or is it a bug?。

编辑:

通过定义我自己的 Add 方法,该方法接受动态参数并返回动态结果,这已尽我所能。

    class Program

    static void Main(string[] args)
    

        var x = Expression.Parameter(typeof(object), "x");
        var y = Expression.Parameter(typeof(object), "y");
         Func<dynamic, dynamic, dynamic> f =
             Expression.Lambda<Func<dynamic, dynamic, dynamic>>(
                 Expression.Call(typeof(Program), "Add", null, x, y),
                 new[]  x, y 
             ).Compile();

       Console.WriteLine(f(5, 2));
       Console.ReadKey();
    

    public static dynamic Add(dynamic x, dynamic y)
    
        return x + y;
    

【讨论】:

我认为这是错误的。您提到的编译器错误(我知道)表明它是 C# 限制,但不一定是表达式树本身的限制。毕竟,C# 也不允许在表达式树上下文中使用 ifwhile(或一般的语句 lambda),但您可以手动构建这样的表达式树(在 .NET 4 中)。但我认为有可能的主要原因是因为有Expression.Dynamic。我很确定答案会涉及到 - 我只是不知道如何,确切地说(而且文档很薄)。 顺便说一句,在您链接到的问题中,Eric Lippert 有一条评论说:“我们在编译时为那些动态操作生成的代码生成器在运行时实现动态语义非常复杂;足够复杂,没有简单的方法可以在表达式树中干净地表示它。” - 注意突出显示的部分。所以它是可能的,只是足够复杂以至于它因为成本太高而价值太低而被削减。 编译器限制或其他......现在不可能。尝试愉快!你必须在 IL 中完成。正如你所说,Eric Lippert 写道,他们忽略了该功能。 再一次,我不是要求使用 C#“表达式树 lambdas”(x =&gt; y 形式)来执行此操作。我要求使用Expression 类公开的一系列方法调用来执行此操作。后者如何完成并不重要 - 如果它可以在 IL 中完成那种方式,它也可以在 C# 中完成。 Eric 专门讨论的是 lambda,而不是一般的表达式树。【参考方案3】:

非常有趣。我想出于同样的原因,以下内容无法编译是不可能的:

Expression<Func<dynamic, dynamic, int>> func = (p1, p2) => p1 + p2;

这是一个编译器错误 CS1963(MS 似乎没有记录):

错误 CS1963:表达式树可能不包含动态操作

【讨论】:

它看起来像 C# 编译器限制,而不像表达式树 API 限制。有关详细信息,请参阅对 Richard 的回复。

以上是关于表达式树中的 C# 4“动态”的主要内容,如果未能解决你的问题,请参考以下文章

.NET 3.5 表达式树中的赋值

有没有办法在 lambda 表达式树中使用“动态”?

C#中的表达式树简介

Roslyn 语法树中的各种语法节点及每个节点的含义

在表达式树中使用可为空的类型

表达式树 Expression Trees