为什么这个 LINQ 查询能够编译?

发布于 2024-09-25 18:44:00 字数 1091 浏览 7 评论 0原文

阅读“奇怪的查询表达式”后作者: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;
    }
}

After reading "Odd query expressions" by Jon Skeet, I tried the code below.
I expected the LINQ query at the end to translate to int query = proxy.Where(x => x).Select(x => x); which does not compile because Where returns an int. The code compiled and prints "Where(x => x)" to the screen and query is set to 2. Select is never called, but it needs to be there for the code to compile. What is happening?

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;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

晨光如昨 2024-10-02 18:44:00

这是因为您的“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

It's because your "select x" is effectively a no-op - the compiler doesn't bother putting the Select(x => x) call at the end. It would if you removed the where clause though. Your current query is known as a degenerate query expression. See section 7.16.2.3 of the C# 4 spec for more details. In particular:

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.

So, three translations (regardless of data source)

// 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
打小就很酷 2024-10-02 18:44:00

它可以编译,因为 LINQ 查询语法是词法替换。编译器

int query = from x in proxy
            where x
            select x;

只有

int query = proxy.Where(x => x);     // note it optimises the select away

然后才检查WhereSelect方法是否确实存在于proxy。因此,在您给出的具体示例中,实际上不需要存在 Select 来进行编译。

如果你有这样的东西:

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

那么它会变成:

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

并且Select方法将被调用。

It compiles because the LINQ query syntax is a lexical substitution. The compiler turns

int query = from x in proxy
            where x
            select x;

into

int query = proxy.Where(x => x);     // note it optimises the select away

and only then does it check whether the methods Where and Select actually exist on the type of proxy. Accordingly, in the specific example you gave, Select does not actually need to exist for this to compile.

If you had something like this:

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

then it would get changed into:

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

and the Select method would be called.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文