Вычислить выражение Lambda как часть дерева выражений

c# expression-trees lambda

Вопрос

Я пытаюсь построить выражение лямбда вверх, используя деревья выражений. Это формат выражения лямбда, которое я пытаюсь создать:

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

Уловка заключается в том, что я не строю это дерево с нуля - логика форматирования уже передана мне в виде экземпляра Expression<Func<DateTime, string>> - say:

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

Я знаю, что за пределами дерева выражений я могу позвонить

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

для оценки выражения - но проблема в том, что я хочу оценить и назначить его в дереве выражений, что позволяет мне выполнять дополнительную логику результата в дереве выражений.

Ничто из того, что я до сих пор не придумал, похоже, делает поездку - почти наверняка до неправильного понимания того, как работают деревья выражений. Любая помощь очень ценится!

Принятый ответ

Итак, если я правильно вас понимаю, вы хотите создать лямбда (выражение), которое использует вашу переданную функцию и выполняет некоторую дополнительную работу вокруг нее. Поэтому вы просто хотите использовать эту функцию внутри выражения.

На этом этапе позвольте мне предположить, что вы даже не используете выражения. Вы можете просто создать функцию, которая принимает параметр Func<DateTime, string> и использует это для обработки чего-либо. Но в случае, если вам действительно нужно выражение для чего-то, я просто попытаюсь объяснить, как его построить.

Для этого примера я создам эту функцию:

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

Как вы можете видеть, я собираюсь создать Func<int, int, string> который затем создает объект DateTime и передает его функции, а затем дополнительно изменяет результат.

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

Посмотрев на ответ Алекса, кажется, что я неправильно понял ваш вопрос и попытался решить обратное тому, что вы делаете. Но это не слишком отличается, чтобы изменить его на то, что вы на самом деле пытаетесь сделать:

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

Но я все же утверждаю, что нормальная функция, которая принимает параметр Func<DateTime, string> и DateTime будет намного проще в обслуживании. Поэтому, если вам действительно не нужны выражения, избегайте их.


Почему я до сих пор не думаю, что вам действительно нужны выражения для этого. Рассмотрим этот пример:

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

Как вы можете видеть, я только один раз создаю логическую функцию форматирования, лениво, когда она еще не установлена. Это, когда отражение работает, чтобы получить некоторые значения, которые вы используете где-то в функции. Поскольку функция создается как лямбда-функция, переменные, которые мы используем из локальной области внутри лямбда-функции, автоматически захватываются и сохраняются в наличии.

Теперь, конечно же, вы могли бы создать это как выражение вместо этого и сохранить скомпилированную функцию, но я утверждаю, что делать это, как это, гораздо более читабельным и поддерживаемым.


Популярные ответы

Вы можете использовать:

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

Затем у вас есть свое выражение:

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

И результат:

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

// 1.2.2003 Hello!


Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему