Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I create the same Expression Tree manually that my straight lambda produces

I've gone through and beat my head against the wall for a while now searched on various phrases and keywords but I cannot find anything close to an answer so i'm hoping someone here can shed some light.

Basically I'm working on diving pretty deep into manipulating, creating, and modifying Expression Trees in C# 4.0

I came across an odd anomaly I cannot make sense of

if I write something like this

Expression<Func<string,string>> InsertAString = (Insert) => "This " + (Insert == "" ? "" : Insert + " ") + "That";

When I get debug and look at the expression tree it looks similar to this

  • F (NodeType = Lambda)
    • Body (NodeType = Add)
      • Left (NodeType = Add)
        • Left (NodeType = Constant, Value = "This ")
        • Right (NodeType = Conditional)
          • IfFalse (NodeType = Add)
            • Left (NodeType = Parameter, Name = "Insert")
            • Right (NodeType = Constant, Value = " ")
          • IfTrue (NodeType = Constant, Value = "")
          • Test (NodeType = Equal)
            • Left (NodeType = Parameter, Name = "Insert")
            • Right (NodeType = Constant, Value = "")
      • Right (NodeType = Constant, Value = "That")
    • Paramerters (Count = 1)
      • Parameters[0] (NodeType = Parameter, Name = "Insert")

I can call

Console.WriteLine(InsertAString.Compile()("Is Something In-between"));

And I get out as I expect

"This is something In-between That"

Now if i try and rebuild that manually using the static methods of the Expression base class I run into an interesting issue. (I have broken down each step into its own Expression for debugging purposes)

ParameterExpression Insert = Expression.Parameter(typeof(object), "Insert");
ConstantExpression This = Expression.Constant("This ");
ConstantExpression That = Expression.Constant("That");
ConstantExpression Space = Expression.Constant(" ");
ConstantExpression NoCharacter = Expression.Constant("");
BinaryExpression InsertPlusSpace = Expression.Add(Insert,Space);
BinaryExpression InsertEqualsNoCharacter = Expression.Equal(Insert,NoCharacter);
ConditionalExpression InsertPlusSpaceOrNothing = Expression.IfThenElse(InsertEqualsNoCharacter,NoCharacter,InsertPlusSpace);
BinaryExpression ThisPlusInsertPlusSpaceOrNothing = Expression.Add(This,InsertPlusSpaceOrNothing);
BinaryExpression ThisPlusInsertPlusSpaceOrNothingPlusThat = Expression.Add(ThisPlusInsertPlusSpaceOrNothing, That);
Lambda Lambda = Expression.Lambda(ThisPlusInsertPlusSpaceOrNothingPlusThat, Middle);
Expression<Func<string,string>> InsertAString = Lambda as Expression<Func<string,string>>   

That based on the values of the generated Expression tree above recreate the same basic expression tree as above (at least with the same "Look")

Everything steps through fine until you get to this line

BinaryExpression InsertPlusSpace = Expression.Add(Insert,Space);

The compiler throws an InvalidOperationException was unhandled

The binary operator Add is not defined for 'System.String' and 'System.String'

Now why is this?

Why when I let C# convert a Lambda into an Expression does it obviously use the Add NodeType, and the Types display show it is definitely using System.String yet when i try and do the same manually it will not let the code continue?

As a Final note I've even tried the following:

BinaryExpression InsertPlusSpace = Expression.MakeBinary( ExpressionType.Add,Insert,Space);

Same error.

I'm curious why it seems at least with what i have been able to find so far that string concatenation in expression trees works only if are not trying to build an expression tree manually that adds constants and variables of type System.String.

Thank you all in advance for the responses.

like image 488
TofuBug Avatar asked Oct 04 '10 18:10

TofuBug


1 Answers

Check the documentation: the '+' operator is actually not defined in the String class. I guess the compiler just knows it means "concatenate the strings", and it transforms it into a call to Concat. So when you call Expression.Add, you need to specify the method that implements the operation (in that case the String.Concat method).

I decompiled the expression with Reflector, it gives the following result (reformatted):

ParameterExpression expression2;
Expression<Func<string, string>> expression =
    Expression.Lambda<Func<string, string>>(
        Expression.Add(
            Expression.Add(
                Expression.Constant("This ", typeof(string)),
                Expression.Condition(
                    Expression.Equal(
                        expression2 = Expression.Parameter(typeof(string), "Insert"),
                        Expression.Constant("", typeof(string)),
                        false,
                        (MethodInfo) methodof(string.op_Equality)),
                    Expression.Constant("", typeof(string)),
                    Expression.Add(
                        expression2,
                        Expression.Constant(" ", typeof(string)),
                        (MethodInfo) methodof(string.Concat))),
                (MethodInfo) methodof(string.Concat)),
            Expression.Constant("That", typeof(string)),
            (MethodInfo) methodof(string.Concat)),
        new ParameterExpression[] { expression2 });

(Note that methodof is not an actual operator, its just what Reflector shows for the ldtoken IL instruction. In C# you have to retrieve the method using reflection.)

like image 131
Thomas Levesque Avatar answered Nov 13 '22 01:11

Thomas Levesque