Valuta l'espressione Lambda come parte di Expression Tree

c# expression-trees lambda

Domanda

Sto cercando di costruire un'espressione lambda usando gli alberi di espressione. Questo è il formato dell'espressione lambda che sto cercando di creare:

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

Il problema è che non sto costruendo questo albero da zero - la logica di formattazione mi è già passata sotto forma di un'istanza di Expression<Func<DateTime, string>> - say:

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

So che al di fuori dell'albero delle espressioni posso chiamare

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

per valutare l'espressione - ma il problema è che desidero valutarlo e assegnarlo all'interno dell'albero dell'espressione - permettendomi di eseguire una logica aggiuntiva sul risultato all'interno dell'albero dell'espressione.

Nulla di ciò che ho inventato finora sembra fare il viaggio, quasi certamente fino a un malinteso su come funzionano gli alberi di espressione. Qualsiasi aiuto molto apprezzato!

Risposta accettata

Quindi, se ti capisco correttamente, vuoi creare un lambda (espressione) che usa la tua funzione passata e fa un lavoro aggiuntivo attorno ad esso. Quindi, in sostanza, vuoi solo usare questa funzione all'interno di un'espressione.

A questo punto, permettimi di suggerire che non usi nemmeno le espressioni. Potresti semplicemente creare una funzione che utilizza un parametro Func<DateTime, string> e la usa per elaborare qualcosa. Ma nel caso tu abbia davvero bisogno dell'espressione per qualcosa, cercherò solo di spiegarti come costruirne uno.

Per questo esempio, creerò questa funzione:

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

Come puoi vedere, creerò un Func<int, int, string> che quindi creerà l'oggetto DateTime e lo passerà alla funzione e quindi cambierà ulteriormente il risultato.

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

Dopo aver guardato la risposta di Alex, sembra che abbia frainteso la tua domanda e abbia cercato di risolvere il contrario di quello che stai facendo. Ma non è tutto troppo diverso per cambiarlo in quello che stai effettivamente cercando di fare:

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

Ma credo che una funzione normale che richiede un parametro Func<DateTime, string> e DateTime sia molto più semplice da mantenere. Quindi, a meno che tu non abbia davvero bisogno delle espressioni, evitalo.


Perché ancora non penso che tu abbia davvero bisogno di espressioni per questo. Considera questo esempio:

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

Come puoi vedere, sto costruendo una volta sola la funzione di logica di formattazione, pigramente quando non è ancora impostata. È lì che scorre la riflessione per ottenere alcuni valori che stai utilizzando da qualche parte nella funzione. Poiché la funzione viene creata come una funzione lambda, le variabili che usiamo dall'ambito locale all'interno della funzione lambda vengono automaticamente catturate e mantenute disponibili.

Ora, naturalmente, potresti anche crearlo come un'espressione e memorizzare la funzione compilata, ma direi che farlo in questo modo è molto più leggibile e mantenibile.


Risposta popolare

Puoi usare:

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

Quindi hai la tua espressione di input:

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

E il risultato:

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

// 1.2.2003 Hello!


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché