7

我已经阅读了这个答案并从中了解了它突出显示的特定情况,即当您在另一个 lambda 中有一个 lambda 并且您不想意外地让内部 lambda 也与外部 lambda 一起编译时。编译外部的 lambda 表达式时,您希望内部 lambda 表达式保持为表达式树。在那里,是的,引用内部 lambda 表达式是有意义的。

但仅此而已,我相信。还有其他引用 lambda 表达式的用例吗?

如果没有,为什么所有 LINQ 运算符,即在类IQueryable<T>中声明的扩展都Queryable引用它们作为参数接收的谓词或 lambda,当它们将信息打包到MethodCallExpression.

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

这是一个方法调用表达式,该方法需要一个 lambda 表达式(而不是委托实例)作为其唯一参数。

MethodCallExpression然后我通过将它包装在一个 lambda 中来编译它。

但这也不会编译内部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());
}
4

1 回答 1

5

您必须将参数作为 a 传递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必须保留有关表达式的所有信息 - 包括ParameterExpressions 和类似的类型和引用。这是因为它们实际上并没有任何事情——它们只是构建表达式树。真正的工作发生在你打电话时queryable.Provider.Execute(expression)。基本上,这就是即使我们在进行组合而不是继承(/interface 实现),也可以保留多态性的方式。但这确实意味着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带有给定谓词的。然而,内部查询实际上将是一个Callto Where,将实际谓词作为参数。

通过确保Where方法的实际调用实际上被转换为Call方法的一个Where,这两种情况变得相同,并且您的 LINQProvider 更简单一点:)

我实际上已经编写了不实现的 LINQ 提供程序IQueryable,并且实际上在方法中具有一些有用的逻辑,例如Where. 它更简单、更高效,但有上述缺点——处理子查询的唯一方法是手动获取“真实InvokeCall谓词表达式。哎呀——这对于一个简单的 LINQ 查询来说是相当大的开销!

当然,它可以帮助您组合不同的可查询提供程序,尽管我实际上还没有看到(m)任何在单个查询中使用两个完全不同的提供程序的示例。

至于和他们之间的区别Expression.ConstantExpression.Quote他们似乎很相似。关键的区别在于Expression.Constant它将任何闭包视为实际常量,而不是闭包。Expression.Quote另一方面,将保留闭包的“封闭性”。为什么?因为闭包对象本身Expression.Constant作为:)传递,并且由于IQueryable树正在执行 [...] 的 lambdas 的 lambdas,因此您真的不想在任何时候丢失闭包语义。

于 2015-05-07T15:58:59.867 回答