Evaluar la expresión Lambda como parte del árbol de expresiones

c# expression-trees lambda

Pregunta

Estoy tratando de construir una expresión lambda usando árboles de expresión. Este es el formato de la expresión lambda que estoy tratando de crear:

Func<DateTime, string> requiredLambda = dt =>
    {
        var formattedDate = dt.ToShortDateString();

        /**
        * The logic is not important here - what is important is that I 
        * am using the result of the executed lambda expression.
        * */
        var builder = new StringBuilder();
        builder.Append(formattedDate);
        builder.Append(" Hello!");
        return builder.ToString();
    };

El problema es que no estoy construyendo este árbol desde cero: la lógica de formato ya se me entregó en forma de una instancia de Expression<Func<DateTime, string>> - di:

Expression<Func<DateTime, string>> formattingExpression = dt => dt.ToShortDateString();

Sé que fuera del árbol de expresiones puedo llamar

formattingExpression.Compile()(new DateTime(2003, 2, 1)) 

para evaluar la expresión, pero el problema es que deseo evaluarla y asignarla dentro del árbol de expresiones, lo que me permite realizar una lógica adicional en el resultado dentro del árbol de expresiones.

Nada de lo que he encontrado hasta ahora parece hacer el viaje, casi con certeza a una mala interpretación de cómo funcionan los árboles de expresión. Cualquier ayuda muy apreciada!

Respuesta aceptada

Entonces, si te entiendo correctamente, quieres crear una lambda (expresión) que use tu función pasada y realice algún trabajo adicional alrededor de ella. Así que esencialmente solo quieres usar esta función dentro de una expresión.

En este punto, permítame sugerir que ni siquiera utiliza expresiones. Simplemente puede crear una función que tome un parámetro Func<DateTime, string> y lo use para procesar algo. Pero en caso de que realmente necesites la expresión para algo, solo intentaré explicar cómo construir uno.

Para este ejemplo, estaré creando esta función:

string MonthAndDayToString (int month, int day)
{
    return "'" + formattingFunction(new DateTime(2013, month, day)) + "'"
}

Como puede ver, voy a crear un Func<int, int, string> que luego crea el objeto DateTime y lo pasa a la función y luego cambia el resultado.

Func<DateTime, string> formatting = dt => (...) // as above

// define parameters for the lambda expression
ParameterExpression monthParam = Expression.Parameter(typeof(int));
ParameterExpression dayParam = Expression.Parameter(typeof(int));

// look up DateTime constructor
ConstructorInfo ci = typeof(DateTime).GetConstructor(new Type[] { typeof(int), typeof(int), typeof(int) });

// look up string.Concat
MethodInfo concat = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string), typeof(string) });

// inner call: formatting(new DateTime(2013, month, day))
var call = Expression.Call(formatting.Method, Expression.New(ci, Expression.Constant(2013), monthParam, dayParam));

// concat: "'" + call + "'"
var expr = Expression.Call(concat, Expression.Constant("'"), call, Expression.Constant("'"));

// create the final lambda: (int, int) => expr
var lambda = Expression.Lambda<Func<int, int, string>>(expr, new ParameterExpression[] { monthParam, dayParam });

// compile and execute
Func<int, int, string> func = lambda.Compile();
Console.WriteLine(func(2, 1)); // '01.02.2013 Hello!'
Console.WriteLine(func(11, 26)); // '26.11.2013 Hello!'

Después de ver la respuesta de Alex, parece que entendí mal tu pregunta y traté de resolver el revés de lo que estás haciendo. Pero no es muy diferente cambiarlo a lo que realmente estás tratando de hacer:

Func<DateTime, string> formatting = dt => dt.ToShortDateString();

ParameterExpression param = Expression.Parameter(typeof(DateTime));
MethodInfo concat = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string), typeof(string) });

var call = Expression.Call(formatting.Method, param);
var expr = Expression.Call(concat, Expression.Constant("'"), call, Expression.Constant(" Hello!'"));
var lambda = Expression.Lambda<Func<DateTime, string>>(expr, new ParameterExpression[] { param });

Func<DateTime, string> func = lambda.Compile();
Console.WriteLine(func(new DateTime(2013, 02, 01)));
Console.WriteLine(func(new DateTime(2013, 11, 26)));

Pero todavía argumentaría que una función normal que toma un Func<DateTime, string> y un parámetro DateTime sería mucho más fácil de mantener. Así que a menos que realmente necesites las expresiones, evítalas.


Por qué todavía no creo que realmente necesites expresiones para esto. Considera este ejemplo:

private Func<DateTime, string> formatting = dt => dt.ToShortDateString();
private Func<DateTime, string> formattingLogic = null;

public Func<DateTime, string> FormattingLogic
{
    get
    {
        if (formattingLogic == null)
        {
            // some results from reflection
            string word = "Hello";
            string quote = "'";

            formattingLogic = dt =>
            {
                StringBuilder str = new StringBuilder(quote);
                str.Append(formatting(dt));

                if (!string.IsNullOrWhiteSpace(word))
                    str.Append(" ").Append(word);

                str.Append(quote);
                return str.ToString();
            };
        }

        return formattingLogic;
    }
}

void Main()
{
    Console.WriteLine(FormattingLogic(new DateTime(2013, 02, 01))); // '01.02.2013 Hello'
    Console.WriteLine(FormattingLogic(new DateTime(2013, 11, 26))); // '26.11.2013 Hello'
}

Como puede ver, solo estoy construyendo la función de lógica de formato una vez, perezosamente cuando aún no está configurada. Eso es cuando la reflexión se ejecuta para obtener algunos valores que está utilizando en algún lugar de la función. A medida que la función se crea como una función lambda, las variables que usamos desde el ámbito local dentro de la función lambda se capturan automáticamente y se mantienen disponibles.

Ahora, por supuesto, también puede crear esto como una expresión y almacenar la función compilada, pero argumentaría que hacerlo así es mucho más fácil de leer y mantener.


Respuesta popular

Puedes utilizar:

Func<Func<DateTime,string>, DateTime, string> requiredLambda = (f, dt) =>
{
    var formattedDate = f.Invoke(dt);

    /**
    * The logic is not important here - what is important is that I 
    * am using the result of the executed lambda expression.
    * */
    var builder = new StringBuilder();
    builder.Append(formattedDate);
    builder.Append(" Hello!");
    return builder.ToString();
};

Entonces tienes tu expresión de entrada:

Expression<Func<DateTime, string>> formattingExpression = 
    dt => dt.ToShortDateString();

Y el resultado:

var result = requiredLambda
    .Invoke(formattingExpression.Compile(), new DateTime(2003, 2, 1));

// 1.2.2003 Hello!


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué