C# 在Linq谓词中,编译器是否会优化;标量;调用Enumerable.Min(),还是会为每个项调用它?

C# 在Linq谓词中,编译器是否会优化;标量;调用Enumerable.Min(),还是会为每个项调用它?,c#,linq,optimization,lambda,C#,Linq,Optimization,Lambda,我刚才在看问题“”,想知道Linq谓词的编译器优化 假设我有一个名为名称的列表,我正在寻找字符串长度最短的项目。因此我们有了查询names.Where(x=>x.Length==names.Min(y=>y.Length))(来自上面提到的问题)。很简单 现在,我们知道C#规范不允许您在枚举集合时修改它。因此,我认为从技术上来说,假设对Min()的上述调用每次调用都会返回相同的值是安全的 但是,我的假设是编译器确实无法知道Enumerable.Min扩展方法中的lambda返回什么。例如,因为我

我刚才在看问题“”,想知道Linq谓词的编译器优化

假设我有一个名为
名称的
列表
,我正在寻找字符串长度最短的项目。因此我们有了查询
names.Where(x=>x.Length==names.Min(y=>y.Length))
(来自上面提到的问题)。很简单

现在,我们知道C#规范不允许您在枚举集合时修改它。因此,我认为从技术上来说,假设对
Min()
的上述调用每次调用都会返回相同的值是安全的

但是,我的假设是编译器确实无法知道
Enumerable.Min
扩展方法中的lambda返回什么。例如,因为我们可以:

int i = 0;
return names.Where(x => x.Length == names.Min(y => ++i));
这意味着所讨论的查询实际上是O(n²)-
Min()
的结果将为每次迭代计算。要获得所需的O(n)实现,您必须明确:

int minLength = names.Min(y => y.Length);
return names.Where(x => x.Length == minLength);
我的假设是正确的,还是Linq或C规范有什么特别之处,允许编译器查看lambda内部并优化对
Min()
的调用


@斯彭德是绝对正确的。考虑下面的片段:

List<string> names = new List<string>(new[] { "r", "abcde", "bcdef", "cdefg", "q" });
return names.Where(x => 
{
    bool b = (x.Length == names.Min(y => y.Length)); 
    names = new List<string>(new[] { "ab" }); 
    return b; 
});
List name=新列表(新[]{“r”、“abcde”、“bcdef”、“cdefg”、“q”});
返回名称。其中(x=>
{
boolb=(x.Length==names.Min(y=>y.Length));
名称=新列表(新[]{“ab”});
返回b;
});
这将只返回“r”,而不是“q”,因为当对
名称
的旧引用被迭代时(foreach
x
),第一次迭代后对
Min
的调用实际上是用
名称
的新实例调用的。但是,查看问题顶部的查询的人可以肯定地说,没有任何内容会被修改。因此,我的问题仍然存在:编译器是否足够聪明,能够看到这一点

想知道Linq谓词的编译器优化

C#编译器不知道BCL类型是如何实现的。它可以查看您引用的程序集,但这些程序集可以随时更改。编译器不能假定编译程序将在其上运行的计算机具有相同的二进制文件。因此,C#编译器不能合法地执行这些优化,因为您可以分辨出它们之间的区别

JIT能够进行这样的优化(目前还没有)

现在,我们知道C#规范不允许您修改 在枚举集合时将其删除。所以我相信它在技术上是安全的 假设上面对Min()的调用总是返回相同的值 每一个电话

C#的规范对库一无所知。它根本没有这样说。
IEnumerable
的每个实现都可以决定是否允许这种行为

但是,我的假设是编译器确实无法知道 Enumerable.Min扩展方法内的lambda返回

是的,它可以做任何事。在运行时,JIT可以推断出这样的属性,但事实并非如此。请注意,即使是基本事实也很难推断,因为存在反射、运行时代码生成和多线程等问题

我的假设是正确的,还是Linq或 允许编译器查看 lambda并优化对Min()的调用

没有。LINQ只有库优化。LINQ to对象完全按照您编写的方式执行。其他LINQ提供商的做法不同


如果您想知道JIT是否会执行一些高级优化,那么从.NET 4.5开始,答案通常是否定的。每个过程都会采用一些复杂的语言功能,并将其转换为更简单的语言。在这种转换过程中,经常会丢失上下文。Lambda表达式就是这些步骤之一。每个lambda都转换为类,然后实例化该类,并将其主方法传递给委托。编译过程甚至没有查看lambda内部。所以产生IL代码的编译器甚至不知道有任何lambda,只看到一堆类。而且这些类没有给他足够的信息来推断您的建议。

考虑到名称集合在每次linq迭代之间可能会发生变化,这样一个主要的内联优化可能会打破预期。@spender As
string
s是不变的,任何更改都不会违反枚举期间的修改规则吗?是的,但现在您确实向编译器提出了很多要求。。。“枚举期间修改”规则发生在运行时。现在编译器检测到它了?@spender Point take@lc。我刚刚测试过,AOT编译器没有看到这一点。(我现在懒得检查JIT生成的代码,但很可能也不会执行此优化。)我能想到的一个原因是调用的方法
Min
可能有副作用。我认为在一种语言中保证这种优化的有效性会更容易,这种语言在副作用和表达式纯度方面的契约比C#目前的强得多,至少在某种程度上可以保证动态链接程序集(不同版本)的这些属性。换句话说,LINQ不是编写性能差代码的借口。通常可以安全地假设序列上的任何方法都是O(N)-记住O(N)表示最坏情况,而不是最佳、平均、实际等。如果LINQ的某些部分决定优化某些调用,例如测试
IEnumerable
是否实现
IList
以优化
Count()
方法,那么这就是开发人员不应该依赖的实现细节。此外,它是库的一个方面,而不是编译器。