为什么要引用Lambda表达式?

7
我已经阅读了this answer并从中理解了它所突出的具体情况,即当您在一个lambda内部有另一个lambda时,您不希望内部lambda也意外地与外部lambda一起编译。当编译外部lambda时,您希望内部lambda表达式仍然保持为表达式树。在这种情况下,引用内部lambda表达式是有意义的。

但我认为就只有这样了。还有其他引用lambda表达式的用例吗?

如果没有,那么为什么所有LINQ操作符(即在Queryable类中声明的IQueryable<T>上的扩展),当它们将谓词或lambda作为参数封装到MethodCallExpression中时,都会引用它们。

我尝试了一个示例(以及过去几天的其他示例),在这种情况下引用lambda似乎毫无意义。

这是对期望lambda表达式(而不是委托实例)作为其唯一参数的方法调用表达式。

然后,我通过将其包装在lambda中来编译MethodCallExpression

但是这不会编译内部的LambdaExpressionGimmeExpression方法的参数)。它将内部lambda表达式保留为表达式树,而不是制作其委托实例。
事实上,不用引号也可以正常工作。
如果我确实引用了该参数,它会出错,并提示我正在向GimmeExpression方法传递错误类型的参数。
这是怎么回事?这个引号有什么意义?
private static void TestMethodCallCompilation()
{
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
        BindingFlags.NonPublic | BindingFlags.Static);

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));

    var methodCallExpression = Expression.Call(null, methodInfo, lambdaExpression);

    var wrapperLambda = Expression.Lambda(methodCallExpression);
    wrapperLambda.Compile().DynamicInvoke();
}

private static void GimmeExpression(Expression<Func<bool>> exp)
{
    Console.WriteLine(exp.GetType());
    Console.WriteLine("Compiling and executing expression...");
    Console.WriteLine(exp.Compile().Invoke());
}
1个回答

5

你需要将参数作为一个ConstantExpression传递:

private static void TestMethodCallCompilation()
{
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
        BindingFlags.NonPublic | BindingFlags.Static);

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));

    var methodCallExpression = 
      Expression.Call(null, methodInfo, Expression.Constant(lambdaExpression));

    var wrapperLambda = Expression.Lambda(methodCallExpression);
    wrapperLambda.Compile().DynamicInvoke();
}

private static void GimmeExpression(Expression<Func<bool>> exp)
{
    Console.WriteLine(exp.GetType());
    Console.WriteLine("Compiling and executing expression...");
    Console.WriteLine(exp.Compile().Invoke());
}

原因很显然-您正在传递一个常量值,因此它必须是一个ConstantExpression。通过直接传递表达式,您明确表示“从这个复杂的表达式树中获取exp的值”。由于该表达式树实际上不返回Expression<Func<bool>>类型的值,因此会出现错误。
IQueryable的工作方式与此并没有太大关系。IQueryable上的扩展方法必须保留有关表达式的所有信息-包括ParameterExpression的类型和引用等。这是因为它们实际上并不执行任何操作-它们只是构建表达式树。真正的工作发生在调用queryable.Provider.Execute(expression)时。基本上,这就是如何保留多态性,即使我们进行组合而不是继承(/接口实现)。但是这意味着IQueryable扩展方法本身不能做任何快捷方式-它们不知道IQueryProvider实际上将如何解释查询,因此它们无法丢弃任何内容。
最重要的好处是,您可以组合查询和子查询。考虑以下查询:
from item in dataSource
where item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2
select item;

现在,这被翻译成类似于以下内容:
dataSource.Where(item => item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2);

外部查询很明显 - 我们将得到一个带有给定谓词的Where。然而,内部查询实际上将是对Where的调用,将实际的谓词作为参数。
通过确保Where方法的实际调用实际上被翻译成Where方法的调用,这两种情况变得相同,你的LINQProvider就会简单得多:)
我实际上编写了不实现IQueryable的LINQ提供程序,并且这些提供程序在像Where这样的方法中实际上有一些有用的逻辑。它更简单、更高效,但有上面描述的缺点 - 处理子查询的唯一方法是手动Invoke Call表达式以获取“真正”的谓词表达式。天啊 - 这对于一个简单的LINQ查询来说是相当大的开销!
当然,它可以帮助您组合不同的可查询提供程序,尽管我实际上没有看到在单个查询中使用两个完全不同的提供程序的(m)任何示例。
至于Expression.ConstantExpression.Quote本身之间的区别,它们似乎非常相似。关键的区别在于,Expression.Constant将任何闭包都视为实际的常量,而不是闭包。另一方面,Expression.Quote将保留闭包的“闭包性”。为什么?因为闭包对象本身也被传递为Expression.Constant :) 由于IQueryable树正在进行lambda的lambda的lambda的[...],所以您真的不想在任何时候失去闭包语义。

非常感谢。我已经做了几个月了。在过去的几个月里,通过大量的例子和深思熟虑,终于有所领悟。我对其中的原因有一些理论,其中一些是正确的。你的答案也帮助了我。 - Water Cooler v2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接