views:

535

answers:

3

Hi All,

During my work with expression trees for a few days now, I came across something that I find difficult to understand; hopefully someone will be able so shed some light here.

If you code Expression<Func<dynamic, dynamic>> expr1 = x => 2 * x; the compiler will complain and you won't get anywhere. However, it seems that if you create one such expression through a method then the compiler seems to be happy about it and the resulting app works. This doesn't make sense, so I'm wondering what goes on behind the curtains.

I suppose that, under the hood, the expression returned by ConvertExpression is perhaps of type Expression<Func<object, object>>, which is a valid type, but it puzzles me that I can't use Expression<Func<dynamic, dynamic>> type in a declaration and yet I can use it as the return type of a method. See an example below.

Thanks a lot!

public class ExpressionExample
{
    public void Main()
    {
        // Doesn't compile:
        //Expression<Func<dynamic, dynamic>> expr1 = x => 2 * x;

        // Compiles and works - OK
        Expression<Func<double, double>> expr2 = x => 2 * x;
        Func<double, double> func2 = (Func<double, double>)expr2.Compile();
        Console.WriteLine(func2(5.0).ToString()); // Outputs 10

        // Compiles and works - ??? This is the confusing block...
        Expression<Func<dynamic, dynamic>> expr3 = ConvertExpression(expr2);
        Func<dynamic, dynamic> func3 = (Func<dynamic, dynamic>)expr3.Compile();
        Console.WriteLine(func3(5.0).ToString()); // Outputs 10

        // Side note: compiles and works:
        Expression<Func<object, object>> expr4 = x => double.Parse(2.ToString()) * double.Parse(x.ToString());
        Func<object, object> func4 = (Func<object, object>)expr4.Compile();
        Console.WriteLine(func4(5.0).ToString()); // Outputs 10
    }

    private Expression<Func<dynamic, dynamic>> ConvertExpression<TInput, TOutput>(Expression<Func<TInput, TOutput>> expression)
    {
        Expression<Func<object, TInput>> convertToInput = value => (TInput)value;
        // The following doesn't compile: var input = Expression.Parameter(typeof(dynamic), "input");

        var input = Expression.Parameter(typeof(object), "input");        

        Expression<Func<TOutput, dynamic>> convertToOutput = value => (dynamic)value;

        var body = Expression.Invoke(convertToOutput, Expression.Invoke(expression, Expression.Invoke(convertToInput, input)));
        var lambda = Expression.Lambda<Func<dynamic, dynamic>>(body, input);

        return lambda;
    }
}
+1  A: 

The compiler error I got when I tried your code was "error CS1963: An expression tree may not contain a dynamic operation". I changed the problem line to Expression<Func<dynamic, dynamic>> expr1 = x => x; (removing the "operation" from the lambda) and it worked! So you are allowed to have dynamics in expressions, but you can't actually perform any "operations" on them. Not very helpful, I know. In my testing, even .ToString() counts as an operation.

Tim Danner
Thanks - I see the same... That makes things even more bizarre I think - So, if there are some operations, then it doesn't compile. However, if there aren't any, then it compiles. Doesn't that contradict the philosophy behind the dynamic type of resolving operations, method invocations, etc. at runtime?
d.
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. Basically, the choice came down to "don't allow them in expression trees", or "ship late", or "ship buggy". We chose the former. We can always add this as a feature in future versions if there's enough demand; if you can describe the compelling user scenarios that motivates this feature, that will help when it comes time to budget the feature work.
Eric Lippert
If anyone opens up a connect post on this I'll vote for it.
Chris Marisic
+4  A: 

I suppose that, under the hood, the expression returned by ConvertExpression is perhaps of type Expression<Func<object, object>>, which is a valid type

Correct.

I can't use Expression<Func<dynamic, dynamic>> type in a declaration and yet I can use it as the return type of a method.

This portion of the statement is incorrect. As you note in your example, it is perfectly legal to use that type in the declaration of a local variable.

The bit that is not legal is the execution of a dynamic operation inside a lambda that is being converted to an expression tree type. The specific expression tree type is irrelevant; what matters is that the operation is dynamic.

Eric Lippert
Thanks a lot for the answer and comment - That solves it!
d.
A: 

Dynamics in Expression Trees - Solution Here:

http://stackoverflow.com/questions/3562088/c-4-dynamic-in-expression-trees

Ryan D. Hatch