在阅读Jon Skeet的“Odd query expressions”后,我尝试了下面的代码。
我期望最后的LINQ查询转换为int query = proxy.Where(x => x).Select(x => x);
,因为Where
返回int
而无法编译。代码编译并将“Where(x => x)”打印到屏幕并将查询设置为2.从不调用Select,但需要在那里编译代码。发生了什么事?
using System;
using System.Linq.Expressions;
public class LinqProxy
{
public Func<Expression<Func<string,string>>,int> Select { get; set; }
public Func<Expression<Func<string,string>>,int> Where { get; set; }
}
class Test
{
static void Main()
{
LinqProxy proxy = new LinqProxy();
proxy.Select = exp =>
{
Console.WriteLine("Select({0})", exp);
return 1;
};
proxy.Where = exp =>
{
Console.WriteLine("Where({0})", exp);
return 2;
};
int query = from x in proxy
where x
select x;
}
}
答案 0 :(得分:12)
这是因为你的“select x”实际上是一个无操作 - 编译器不会在最后调用Select(x => x)
调用。如果您删除where
子句,它将。您当前的查询称为简并查询表达式。有关详细信息,请参阅C#4规范的第7.16.2.3节。特别是:
简并查询表达式是一种简单地选择源元素的表达式。翻译的后期阶段通过用其源替换它们来移除由其他翻译步骤引入的退化查询。但是,确保查询表达式的结果永远不是源对象本身是很重要的,因为这会向查询客户端显示源的类型和标识。因此,此步骤通过在源上显式调用Select来保护直接在源代码中编写的退化查询。然后由Select和其他查询运算符的实现者来确保这些方法永远不会返回源对象本身。
所以,三个翻译(不论数据来源)
// Query // Translation
from x in proxy proxy.Where(x => x)
where x
select x
from x in proxy proxy.Select(x => x)
select x
from x in proxy proxy.Where(x => x)
where x .Select(x => x * 2)
select x * 2
答案 1 :(得分:7)
它编译是因为LINQ查询语法是词汇替换。编译器转为
int query = from x in proxy
where x
select x;
到
int query = proxy.Where(x => x); // note it optimises the select away
并且只有然后会检查方法Where
和Select
是否实际存在于proxy
类型上。因此,在您给出的具体示例中,Select
实际上并不需要存在以进行编译。
如果你有这样的事情:
int query = from x in proxy
select x.ToString();
然后它会变成:
int query = proxy.Select(x => x.ToString());
将调用Select
方法。