Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Evaluate Lambda Expression as part of Expression Tree

I am trying to build a lambda expression up using expression trees. This is the format of the lambda expression I am trying to create:

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

The catch is that I am not building this tree up from scratch - the formatting logic is already handed to me in the form of an instance of Expression<Func<DateTime, string>> - say:

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

I know that outside of the expression tree I can call

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

to evaluate the expression - but the issue is that I wish to evaluate and assign it within the expression tree - allowing me to perform additional logic on the result within the expression tree.

Nothing I have come up with so far seems to do the trip - almost certainly down to a misunderstanding of how expression trees work. Any help greatly appreciated!

like image 751
Lawrence Avatar asked Oct 02 '22 08:10

Lawrence


2 Answers

So, if I understand you correctly, you want to create a lambda (expression) which uses your passed function and does some additional work around it. So you essentially just want to use this function inside of an expression.

At this point, allow me to suggest that you don’t even use expressions. You could just create a function that takes a Func<DateTime, string> parameter and uses that to process something. But in case you really need the expression for something, I’ll just try to explain how to build one.

For this example, I’ll be creating this function:

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

As you can see, I’m going to create a Func<int, int, string> which then creates the DateTime object and passes it to the function and then further changes the result.

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

After looking at Alex’ answer, it seems that I misunderstood your question and tried to solve the reverse of what you are doing. But it’s not all too different to change it to what you are actually trying to do:

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

But I’d still argue that a normal function that takes a Func<DateTime, string> and a DateTime parameter would be a lot easier to maintain. So unless you really need the expressions, avoid them.


Why I still don’t think you really need expressions for this. Consider this example:

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

As you can see, I’m only constructing the formatting logic function once, lazily when it’s not yet set. That’s when the reflection runs to get some values you are using somewhere in the function. As the function is created as a lambda function, the variables we use from the local scope within the lambda function are automatically captured and kept available.

Now of course you could also create this as an expression instead and store the compiled function, but I’d argue that doing it like this is a lot more readable and maintainable.

like image 187
poke Avatar answered Oct 13 '22 11:10

poke


You may use:

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

Then you have your input expression:

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

And the result:

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

// 1.2.2003 Hello!
like image 31
Alex Filipovici Avatar answered Oct 13 '22 11:10

Alex Filipovici