Почему я не могу создать то же дерево выражений вручную, которое производит моя прямая лямбда? - PullRequest
9 голосов
/ 04 октября 2010

Я прошел некоторое время и бился головой о стену, а теперь искал различные фразы и ключевые слова, но я не могу найти ничего похожего на ответ, поэтому я надеюсь, что кто-то здесь может пролить свет.

В основном я работаю над глубоким погружением в манипулирование, создание и изменение деревьев выражений в C # 4.0

Я столкнулся со странной аномалией, которую я не могу понять

, если я что-то напишукак это

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

Когда я получаю отладку и смотрю на дерево выражений, оно выглядит примерно так:

  • F (NodeType = Lambda)
    • Body (NodeType)= Добавить)
      • Слева (NodeType = Добавить)
        • Слева (NodeType = Константа, Значение = "Это")
        • Справа (NodeType = Условно)
          • IfFalse (NodeType = Add)
            • Слева (NodeType = Параметр, Имя = "Вставить")
            • Справа (NodeType = Константа, Значение = "")
          • IfTrue (NodeType = Constant, Value = "")
          • Test (NodeType = Equal)
            • Слева (NodeType = Параметр, Имя = "Вставить")
            • Справа (NodeType = Константа, Значение = "")
      • Справа (NodeType = Constant, Value = "That")
    • Параметры (Count = 1)
      • Параметры [0] (NodeType= Параметр, Имя = "Вставить")

Я могу позвонить

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

И выхожу, как я ожидаю

«Это что-то промежуточное»

Теперь, если я попробую перестроить это вручную, используя статические методы базового класса Expression, я столкнусь с интересной проблемой.(Я разбил каждый шаг на собственное выражение для целей отладки)

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>>   

Это основано на значениях сгенерированного выше дерева выражений, воссоздающих то же базовое дерево выражений, что и выше (по крайней мере с тем жеПосмотрите ")

Все проходит нормально, пока не дойдете до этой строки

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

Компилятор создает исключение InvalidOperationException, не обработанное

Бинарный оператор Add не являетсяопределенные для 'System.String' и 'System.String'

Теперь, почему это так?

Почему, когда я позволяю C # преобразовать лямбда-выражение в выражение, оно, очевидно, использует AddNodeType, и дисплей Types показывает, что он определенно использует System.String, но когда я пытаюсь сделать то же самое вручную, он не позволит продолжить код?

В качестве заключительного замечания я даже попробовал следующее:

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

Та же ошибка.

Мне любопытно, почему, по крайней мере, с тем, что мне удалось найти, работает конкатенация строк в деревьях выраженийТолько если вы не пытаетесь построить дерево выражений вручную, которое добавляет константы и переменные типа System.String.

Заранее всем спасибо за ответы.

1 Ответ

7 голосов
/ 04 октября 2010

Проверьте документацию: оператор '+' фактически не определен в классе String. Я предполагаю, что компилятор просто знает, что означает «объединить строки», и он преобразует его в вызов Concat. Поэтому, когда вы вызываете Expression.Add, вам нужно указать метод, который реализует операцию (в этом случае метод String.Concat).

Я декомпилировал выражение с помощью Reflector, оно дает следующий результат (переформатированный):

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

(Обратите внимание, что methodof не является действительным оператором, это то, что Reflector показывает для инструкции ldtoken IL. В C # вы должны извлечь метод, используя отражение.)

...