¿Por qué no puedo crear manualmente el mismo Árbol de expresiones que produce mi lambda recta?

c# expression-trees lambda

Pregunta

He pasado y golpeé mi cabeza contra la pared durante un tiempo, busqué varias frases y palabras clave, pero no encuentro nada cerca de una respuesta, así que espero que alguien aquí pueda arrojar algo de luz.

Básicamente estoy trabajando en el buceo a fondo para manipular, crear y modificar Árboles de Expresión en C # 4.0

Me encontré con una extraña anomalía que no puedo entender.

si escribo algo como esto

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

Cuando obtengo la depuración y veo el árbol de expresiones, se parece a esto

  • F (NodeType = Lambda)
    • Cuerpo (NodeType = Agregar)
      • Izquierda (NodeType = Agregar)
        • Izquierda (NodeType = Constante, Valor = "Este")
        • Derecha (NodeType = Condicional)
          • IfFalse (NodeType = Add)
            • Izquierda (NodeType = Parámetro, Nombre = "Insertar")
            • Derecha (NodeType = Constante, Valor = "")
          • IfTrue (NodeType = Constant, Value = "")
          • Prueba (NodeType = Equal)
            • Izquierda (NodeType = Parámetro, Nombre = "Insertar")
            • Derecha (NodeType = Constante, Valor = "")
      • Derecha (NodeType = Constant, Value = "That")
    • Parámetros (Cuenta = 1)
      • Parámetros [0] (NodeType = Parámetro, Nombre = "Insertar")

puedo llamar

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

Y salgo como espero.

"Esto es algo entre eso"

Ahora, si intento y reconstruyo manualmente utilizando los métodos estáticos de la clase base de Expression, me encuentro con un problema interesante. (He desglosado cada paso en su propia Expresión para propósitos de depuración)

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

Eso, basado en los valores del árbol de Expresión generado arriba, recrea el mismo árbol de expresión básico que el anterior (al menos con el mismo "Aspecto")

Todo pasa bien hasta llegar a esta línea.

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

El compilador arroja una InvalidOperationException no fue manejada

El operador binario Add no está definido para 'System.String' y 'System.String'

Ahora por que es esto

¿Por qué cuando permito que C # convierta un Lambda en una Expresión, obviamente usa el Tipo de Nodo Agregar, y la pantalla de Tipos muestra que definitivamente está usando System.String pero cuando intento hacer lo mismo manualmente no dejará que el código continúe?

Como nota final incluso he intentado lo siguiente:

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

Mismo error.

Tengo curiosidad de por qué parece al menos con lo que he podido encontrar hasta ahora que la concatenación de cadenas en los árboles de expresión solo funciona si no se intenta construir un árbol de expresión manualmente que agregue constantes y variables de tipo System.String.

Gracias a todos por adelantado por las respuestas.

Respuesta popular

Verifique la documentación: el operador '+' en realidad no está definido en la clase String . Supongo que el compilador simplemente sabe que significa "concatenar las cadenas", y lo transforma en una llamada a Concat . Entonces, cuando llama a Expression.Add , necesita especificar el método que implementa la operación (en ese caso, el método String.Concat ).

Descompilé la expresión con Reflector, da el siguiente resultado (reformateado):

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

(Tenga en cuenta que methodof no es un operador real, es solo lo que Reflector muestra para la instrucción IL ldtoken . En C #, tiene que recuperar el método utilizando la reflexión.)



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow