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

50

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

考虑以下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)生成相应的等价代码?

3个回答

58

您可以通过将动态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的问题,但您肯定是在以他期望的方式解决它。 - Kirk Woll
太棒了,这正是我想要的,谢谢!从 MSDN 文档(http://msdn.microsoft.com/en-us/library/ee814532.aspx)中查看 Binder.BinaryOperation,还有一件事不太清楚——“context”参数的含义是什么?C# 是否总是在那里使用封闭类型的名称?它是否具有除标记特定调用站点与所有其他调用站点不同(为缓存目的,我假设)之外的任何特殊语义含义?如果我在同一个类中有两个可能以不同方式分派相同内容的方法,我需要创建两个虚拟类吗? - Pavel Minaev
@Pavel:我认为它用于确定哪些成员是可访问的。例如,如果你有private void Foo(string s) { } public void Foo(object o) { },在类内部调用字符串的Foo将选择字符串重载,在类外部将选择对象重载。 - Quartermeister
@Pavel:不用谢!顺便说一下,采纳答案不会授予悬赏,所以它仍然是开放的。您还有其他需求吗? - Quartermeister
@Quartermeister:抱歉,我很少使用赏金功能,所以我错过了它不是自动的这一事实。我暂时没有进一步的问题,就这样吧。 - Pavel Minaev

2

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

例如,以下代码将无法编译,因为它使用了 + 操作符,试图构建违反此规则的表达式树:

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

如果您不进行添加操作,那么您可以摆脱它。
请参见如何创建Expression<Func<dynamic,dynamic>> - 或者这是一个错误?以获取更多信息。
编辑:
通过定义自己的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;
    }
}

2
我认为这是错误的。你提到的编译器错误(我知道)表明这是C#的限制,但不一定是表达式树本身的限制。毕竟,在表达式树上下文中,C#也不允许使用ifwhile(或一般的语句lambda),但是您可以手动构建这样的表达式树(在.NET 4中)。但我相信它可能是可能的主要原因是因为有Expression.Dynamic。我很确定答案涉及到那个 - 我只是不知道如何做到这一点(文档很薄)。 - Pavel Minaev
1
顺便提一句,在你链接的那个问题中,Eric Lippert有一个评论:“我们为这些动态操作生成的代码在编译时实现了运行时的动态语义,这是非常复杂的;足够复杂,以至于没有 简单 的方式来 清晰地 表示它在表达式树中。”-请注意被标记的部分。所以这是可能的,只不过足够复杂,被取消是因为代价太高而收益过低。 - Pavel Minaev
编译器限制或其他原因……现在不可能实现。尽情尝试吧!你必须在IL中完成它。正如你所说,Eric Lippert写道,他们没有包含那个功能。 - Richard Anthony Freeman-Hein
4
我不是要求使用C#的“表达式树lambda”(即x => y形式),而是要求使用Expression类公开的一系列方法调用来完成此操作。后者如何完成并不重要——如果在IL中可以以那种方式完成,那么在C#中也可以完成。Eric具体谈论的是lambda表达式,而不是一般的表达式树。 - Pavel Minaev

1
非常有趣。我猜由于相同的原因,以下内容也无法编译:
Expression<Func<dynamic, dynamic, int>> func = (p1, p2) => p1 + p2;

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

错误 CS1963:表达式树中不允许包含动态操作


这似乎是 C# 编译器的限制,而不像是表达式树 API 的限制。有关详细信息,请参见对 Richard 的响应。 - Pavel Minaev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接