我已经阅读了this answer,并从中了解了它突出显示的具体情况,即当你在另一个lambda中有一个lambda并且你不想意外地让内部lambda也编译时与外面的。编译外部时,您希望内部lambda表达式保留为表达式树。在那里,是的,引用内在的lambda表达是有道理的。
但我相信这是关于它的。引用lambda表达式还有其他用例吗?
如果没有,为什么所有LINQ运算符,即IQueryable<T>
类中声明的Queryable
上的扩展引用它们作为参数接收的谓词或lambdas将该信息打包在MethodCallExpression
。
我尝试了一个例子(以及过去几天的其他几个例子),在这种情况下引用lambda似乎没有任何意义。
这是一个方法调用表达式,该方法需要一个lambda表达式(而不是委托实例)作为唯一参数。
然后我将MethodCallExpression
包装在lambda中进行编译。
但是,这并没有编译内部LambdaExpression
(GimmeExpression
方法的参数)。它将内部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());
}
答案 0 :(得分: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)
时,真正的工作就会发生。基本上,即使我们正在进行组合,而不是继承(/ interface implementation),这就是多态性的保留方式。但它确实意味着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
。但是,内部查询实际上将是Call
到Where
,将实际谓词作为参数。
通过确保Where
方法的实际调用实际上被转换为Call
方法的Where
,这两种情况都变得相同,而您的LINQProvider就是那个有点简单:)
我实际编写的LINQ提供程序没有实现IQueryable
,并且实际上在Where
等方法中有一些有用的逻辑。它更简单,更有效,但有上述缺点 - 处理子查询的唯一方法是手动Invoke
Call
表达式来获得“真正的”谓词表达式。对于一个简单的LINQ查询来说,这是一个相当大的开销!
当然,它可以帮助您组合不同的可查询提供程序,尽管我实际上没有看到(m)在单个查询中使用两个完全不同的提供程序的任何示例。
至于Expression.Constant
和Expression.Quote
之间的区别,它们看起来非常相似。关键的区别在于Expression.Constant
会将任何闭包视为实际的常量,而不是闭包。另一方面,Expression.Quote
将保留闭包的“封闭性”。为什么?因为闭包对象本身也传递为Expression.Constant
:)并且因为IQueryable
树正在做lambdas of lambdas of lambdas [...],你真的不希望在任何时候丢失闭包语义。