【问题标题】:Evaluate Lambda Expression as part of Expression Tree评估 Lambda 表达式作为表达式树的一部分
【发布时间】:2013-11-26 10:49:36
【问题描述】:

我正在尝试使用表达式树构建一个 lambda 表达式。这是我要创建的 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&lt;Func&lt;DateTime, string&gt;&gt; 实例的形式交给我 - 比如说:

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

我知道表达式树的外部我可以调用

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

评估表达式 - 但问题是我希望在表达式树内评估并分配它 - 允许我对表达式树内的结果执行额外的逻辑。

到目前为止,我想出的任何东西似乎都无法奏效——几乎可以肯定是因为对表达式树的工作原理存在误解。非常感谢任何帮助!

【问题讨论】:

  • 我不明白。为什么不能简单地在表达式树中调用formattingExpression.Compile()

标签: c# lambda expression-trees


【解决方案1】:

所以,如果我对您的理解正确,您想创建一个 lambda(表达式),它使用您传递的函数并围绕它做一些额外的工作。所以你本质上只是想在表达式中使用这个函数。

在这一点上,请允许我建议您甚至不要使用表达式。您可以创建一个带有Func&lt;DateTime, string&gt; 参数并使用它来处理某些内容的函数。但如果你真的需要某个表达式,我将尝试解释如何构建一个。

对于这个例子,我将创建这个函数:

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

如您所见,我将创建一个 Func&lt;int, int, string&gt;,然后创建 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&lt;DateTime, string&gt;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'
}

如你所见,我只构建了一次格式化逻辑函数,在它还没有设置的时候是懒惰的。那时,反射运行以获取您在函数中某处使用的一些值。由于该函数是作为 lambda 函数创建的,因此我们在 lambda 函数内的本地范围内使用的变量会被自动捕获并保持可用。

当然,您也可以将其创建为表达式并存储编译后的函数,但我认为这样做更具可读性和可维护性。

【讨论】:

  • 该 lambda 可以表示为 .Net 4.0 表达式树,使用 Expression.Block() 之类的东西。虽然您无法直接使用 C# lambda 创建该表达式。
  • @svick 是的,我就是这个意思。现在从我的答案中删除了这部分,因为它并不真正相关:)
  • 很好的答案 - 你不仅回答了我问的问题 - 你回答了我没有问的问题,但 真的 想知道答案(使用函数而不是我的格式化逻辑的表达式)。仅供参考 - 我需要表达式的原因是因为逻辑的构造依赖于反射 - 我需要缓存已编译的函数以提高性能。
  • @Lawrence 很高兴我能帮上忙!请注意,我仍然不认为您真的需要表达式。即使您使用反射动态创建逻辑,您仍然可以创建一个使用反射结果的 lambda 函数。创建 lambda 将自动在其自己的范围内捕获必要的值,因此它将持续存在,并且您可以保留对创建的 lambda 的引用以供重复使用。如果您要编译它,则不需要为它构建表达式树。如果您需要示例,请告诉我。
  • 您不需要表达式可能是对的 - 我需要一些时间来消化您非常好的和深入的答案。出于兴趣,通过表达式方法,我注意到您使用 Expression.Call 的版本作为静态方法。当“格式化”Func 不是静态的(即在运行时注入)时,你会使用什么?
【解决方案2】:

你可以使用:

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!

【讨论】:

  • 感谢您的回答。理想情况下,我想将requiredLambda 的类型保留为Func&lt;DateTime, string&gt;,但如果我找不到解决方法,您的回答肯定很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多