views:

261

answers:

3

I'm trying to figure out how to put all the pieces together, and would appreciate a concrete source code sample for a simple case to start with.

Consider the following C# code:

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

I can produce an equivalent function at runtime using expression trees as follows:

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

Now given the following lambda:

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

how would I generate the equivalent using expression trees (and, presumably, Expression.Dynamic)?

+2  A: 

You cannot do that because an expression tree "May not contain a dynamic operation".

The following will not compile, because of the + operation, for example, and you are trying to build an expression tree that violates that rule:

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

If you were not doing an Add operation you could get away with it.

See http://stackoverflow.com/questions/2046480/net-4-0-how-to-create-an-expressionfuncdynamic-dynamic-or-is-it-a-bug for more information.

Edit:

This is as close as I can get, by defining my own Add method that takes dynamic parameters and returns a dynamic result.

    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;
    }
}
Richard Hein
I think this is wrong. The compiler error you mention (which I'm aware of) indicates that it is a C# limitation, but not necessarily that it is a limitation of expression trees as such. After all, C# does not permit `if` or `while` (or statement lambdas in general) in expression tree context, either, but you can build such an expression tree manually just fine (in .NET 4). But the main reason why I believe it is possible is because there is `Expression.Dynamic`. I'm pretty sure the answer would involve that - I just don't know how, exactly (and documentation is thin).
Pavel Minaev
By the way, in the question to which you've linked, there's a comment from Eric Lippert which goes, "The codegen that we generate for those dynamic operations at compile time that implements dynamic semantics at runtime is exceedingly complex; sufficiently complex that there is no __easy__ way to represent it __cleanly__ in an expression tree." - note the highlighted part. So it's possible, just complicated enough that that it was cut for being too costly and giving too little value.
Pavel Minaev
Compiler limitation or otherwise ... it's not possible right now. Have fun trying! You'll have to do it in IL. As you said Eric Lippert wrote that they left that feature out.
Richard Hein
Again, I'm not asking to do this using C# "expression tree lambdas", which is the `x => y` form. I'm asking to do this using a sequence of calls of methods exposed by the `Expression` class. It doesn't matter how the latter is done - if it can be done _that way_ in IL, it can also be done in C#. And Eric was specifically talking about lambdas, not expression trees in general.
Pavel Minaev
A: 

Very interesting. I guess it's impossible for the same reason the following does not compile:

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

It's a compiler error CS1963 (which doesn't seem to be documented by MS):

error CS1963: An expression tree may not contain a dynamic operation

Igor Zevaka
It looks like a C# compiler limitation, not like an expression tree API limitation. See response to Richard for details.
Pavel Minaev
+9  A: 

You can create an expression tree that represents a dynamic C# addition expression by passing the CallSiteBinder for a dynamic C# addition expression into Expression.Dynamic. You can discover the code to create the Binder by running Reflector on the original dynamic expression. Your example would go something like this:

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();
Quartermeister
Cool, very interesting. Not sure if you've answered the OP's question, but you're certainly tackling it in the way he would wish.
Kirk Woll
Wonderful, this is just what I wanted, thank you! One more thing that is not quite clear to me from looking at MSDN docs for Binder.BinaryOperation (http://msdn.microsoft.com/en-us/library/ee814532.aspx) - what is the meaning of the "context" parameter? Does C# always use the name of the enclosing type there? Does it have any special semantic meaning other than to mark a particular call site as different from all other call sites (for caching purposes, I assume)? If, say, I have two methods in a same class which are likely to dispatch same things differently, do I create two dummy classes?
Pavel Minaev
@Pavel: I think it is used to determine what members are accessible. For example, if you have `private void Foo(string s) { } public void Foo(object o) { }`, calling Foo on a string inside the class will choose the string overload, but outside the class will choose the object overload.
Quartermeister
@Quartermeister: you're right on this as well. Thanks again!
Pavel Minaev
@Pavel: You're welcome! By the way, accepting an answer doesn't award the bounty, so it's still open. Is there something more you're looking for?
Quartermeister
@Quartermeister: sorry, I don't use the bounty feature much, so I missed the fact that it's not automatic. I don't have any further questions on this (for now, anyway), so there it goes.
Pavel Minaev