Évaluer l'expression lambda dans l'arborescence de l'expression

c# expression-trees lambda

Question

J'essaie de construire une expression lambda en utilisant des arbres d'expression. Voici le format de l'expression lambda que j'essaie de créer:

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

Le problème, c'est que je ne construis pas cet arbre à partir de rien - la logique de formatage m'est déjà transmise sous la forme d'une instance d' Expression<Func<DateTime, string>> - par exemple:

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

Je sais qu'en dehors de l'arbre d'expression, je peux appeler

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

pour évaluer l'expression - mais le problème est que je souhaite l'évaluer et l'affecter dans l'arbre des expressions - me permettant d'effectuer une logique supplémentaire sur le résultat dans l'arbre des expressions.

Rien de ce que j'ai imaginé jusqu'ici ne semble faire le voyage - presque certainement à une incompréhension du fonctionnement des arbres d'expression. Toute aide grandement appréciée!

Réponse acceptée

Donc, si je vous ai bien compris, vous voulez créer une lambda (expression) qui utilise votre fonction passée et effectue un travail supplémentaire autour de celle-ci. Donc, vous voulez essentiellement utiliser cette fonction dans une expression.

À ce stade, permettez-moi de suggérer que vous n'utilisez même pas d'expressions. Vous pouvez simplement créer une fonction qui prend un paramètre Func<DateTime, string> et l’utilise pour traiter quelque chose. Mais au cas où vous auriez vraiment besoin de l'expression pour quelque chose, je vais juste essayer d'expliquer comment en construire une.

Pour cet exemple, je vais créer cette fonction:

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

Comme vous pouvez le constater, je vais créer un Func<int, int, string> qui crée ensuite l'objet DateTime et le passe à la fonction, puis modifie le résultat.

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

Après avoir examiné la réponse d’Alex, il semble que j’ai mal compris votre question et que j’ai essayé de résoudre l’inverse de ce que vous faites. Mais ce n'est pas si différent de le changer pour ce que vous essayez réellement de faire:

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

Mais je soutiendrais toujours qu’une fonction normale prenant Func<DateTime, string> et un paramètre DateTime serait beaucoup plus facile à gérer. Donc, à moins que vous n'ayez vraiment besoin d' expressions, évitez-les.


Pourquoi je ne pense toujours pas que vous ayez vraiment besoin d'expressions pour cela. Considérons cet exemple:

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

Comme vous pouvez le constater, je ne construis qu'une seule fois la fonction de logique de formatage, paresseusement lorsqu'elle n'est pas encore définie. C'est à ce moment-là que la réflexion s'exécute pour obtenir certaines valeurs que vous utilisez quelque part dans la fonction. Lorsque la fonction est créée en tant que fonction lambda, les variables que nous utilisons à partir de la portée locale dans la fonction lambda sont automatiquement capturées et maintenues disponibles.

Maintenant, bien sûr, vous pouvez également créer ceci comme une expression et stocker la fonction compilée, mais je dirais que le faire comme ceci est beaucoup plus lisible et maintenable.


Réponse populaire

Vous pouvez utiliser:

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

Ensuite, vous avez votre expression d'entrée:

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

Et le résultat:

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

// 1.2.2003 Hello!


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi