5

在阅读了 Jon Skeet 的“奇怪的查询表达式”之后,我尝试了下面的代码。我希望最后的 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;
    }
}
4

2 回答 2

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
于 2010-09-30T20:09:16.407 回答
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存在Selectproxy. 因此,在您给出的具体示例中,Select实际上并不需要为此编译存在。

如果你有这样的事情:

    int query = from x in proxy
                select x.ToString();

那么它会变成:

int query = proxy.Select(x => x.ToString());

并且该Select方法将被调用。

于 2010-09-30T20:09:37.130 回答