使用 LinQ 查询语法未调用 Select 方法

4

我希望能够在我的类中启用 LinQ 查询语法。 我认为查询语法会被转换为方法语法,例如:

var query = from p in new Class1<Product>()
        where p.Id == "1000"
        select p

被翻译成:

var query = new Class1<Product>().Where(p => p.Id == "1000").Select(p => p);

那么我已经将我的Class1实现为:

public class Class1<T>
{
    public Class1<T> Where(Expression<Func<T, bool>> expression)
    {
        return this;
    }
    public Class1<T> Select<TResult>(Func<T, TResult> expression)
    {
        return this;
    }
}

我已经使用以下代码进行了测试:

static void Main(string[] args)
{
    var query = from p in new Class1<Product>()
                where p.Id == "1000"
                select p;
}

我注意到Select方法没有被调用,但是如果我从LinQ中删除where子句,Select就会被调用:

static void Main(string[] args)
{
    var query = from p in new Class1<Product>()
                // where p.Id == "1000" -> commenting that Select method is called
                select p;
}

有人知道为什么吗?

这里有一个代码片段,你可以在 https://dotnetfiddle.net/JgxKG9 上测试它。

3个回答

10
有人知道为什么吗?
是的,因为语言规范就是这样规定的。查询表达式的翻译都在C# 5规范的第7.16.2节中。
第7.16.2.5节解释了为什么你的初始示例是不正确的 - Select 不会被调用:

A query expression of the form

from x in e select v

is translated into

( e ) . Select ( x => v )

except when v is the identifier x, the translation is simply

( e )

For example

from c in customers.Where(c => c.City == “London”)
select c

is simply translated into

customers.Where(c => c.City == “London”)

然而,对于变态查询表达式,情况并非如此,在7.16.2.3中有所涉及-该部分解释了在删除where子句时会发生什么:

A query expression of the form

from x in e select x

is translated into

( e ) . Select ( x => x )

The example

from c in customers
select c

Is translated into

customers.Select(c => c)

A degenerate query expression is one that trivially selects the elements of the source. A later phase of the translation removes degenerate queries introduced by other translation steps by replacing them with their source. It is important however to ensure that the result of a query expression is never the source object itself, as that would reveal the type and identity of the source to the client of the query. Therefore this step protects degenerate queries written directly in source code by explicitly calling Select on the source. It is then up to the implementers of Select and other query operators to ensure that these methods never return the source object itself.


1
很高兴看到规格部分,你找得非常快,@Jon!! - Ehsan Sajjad
1
@JonSkeet,你知道内存的规格吗?这够快了! :-) - Jcl
1
不是 Jon 在搜索 C# 规范,而是他们在搜索 Jon。 :) - peval27
2
@EhsanSajjad 规格说明书附带在Visual Studio中... 在默认安装中,您可以在C:\Program Files (x86)\Microsoft Visual Studio <version>\VC#\Specifications\1033找到它们。不确定在VS 2017上是否有所更改。 - Jcl
1
@JavierRos 是的,3082 是区域设置 ID(英语为 1033,西班牙语(西班牙)为 3082-其他 [在此处](https://msdn.microsoft.com/es-es/library/ms912047(v=winembedded.10).aspx))。 - Jcl
显示剩余5条评论

6
您的理解有误,以下查询语句:
var query = from p in new Class1<Product>()
        where p.Id == "1000"
        select p

will translate to :

var query = new Class1<Product>().Where(p => p.Id == "1000");

当您删除where部分时:

var query = from p in new Class1<Product>()
            select p;

现在它将被翻译成类似以下的内容:
var query = new Class1<Product>().Select(p=>p);

如果我删除where子句,为什么会被称为什么? - Javier Ros
@Jon比我更好地解释了参考规范 :) - Ehsan Sajjad
1
@EhsanSajjad 我认为这里有一个错误:“当你删除select部分时:”应该是where - peval27
我会点赞是因为你很快,即使你没有参考规格。 - peval27
我知道答案所以出于好奇心无法等待 :D 是的,规格支持更多这种问题的答案,用户会问为什么这个有效而那个不行 :) - Ehsan Sajjad

5

我非常确定从查询语法到方法语法的翻译,如果它说明了一个identity projection,就会优化对Select的调用。

由于“p => p”会将所有内容投射到自身,并且Where子句已经在源序列和结果之间添加了一个抽象层,所以这个调用不再必要。

因此,

var query = from p in new Class1<Product>()
            where p.Id == "1000"
            select p;

仅仅被翻译成

var query = new Class1<Product>().Where(p => p.Id == "1000");

但我承认这只是我的猜测,并且仍在寻找相关的规范部分。
更新:Jon更快了


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