为啥 linq to object 手动实现迭代器?

Posted

技术标签:

【中文标题】为啥 linq to object 手动实现迭代器?【英文标题】:Why is linq to object implementing iterators manually?为什么 linq to object 手动实现迭代器? 【发布时间】:2015-09-12 17:16:51 【问题描述】:

在浏览 .net 核心源代码时,我注意到即使是源代码形式的迭代器类也是手动实现的,而不是依赖于 yield 语句和自动 IEnumerable 实现。

您可以在这一行看到例如 where 迭代器 https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Enumerable.cs#L168 的声明和实现

我假设如果他们经历了这样做而不是简单的 yield 语句的麻烦,那么肯定会有一些好处,但我不能立即看到哪个,这似乎与我记得编译器自动执行的操作非常相似几年前我回顾了 eric lippert 的博客,我记得当我在早期天真地用 yield 语句重新实现 LINQ 以更好地理解它时,性能配置文件类似于 .NET 版本。

这激起了我的好奇心,但这也是一个非常重要的问题,因为我正处于一个相当大的数据中间 - 在内存项目中,如果我遗漏了一些明显可以使这种方法更好的东西,我很想知道权衡.

编辑:澄清一下,我确实理解为什么他们不能只在 where 方法中产生(不同容器类型的不同枚举),我不明白的是为什么他们实现迭代器本身(也就是说,而不是分叉到不同的迭代器,分叉到不同的方法,根据类型进行不同的迭代,并让步以自动实现状态机,而不是手动实现 case 1 goto 2 case 2 等)。

【问题讨论】:

【参考方案1】:

一个可能的原因是专用迭代器执行了一些优化,例如组合选择器和谓词以及利用索引集合。

这些的用处在于,可以以比 yield 的编译器魔法生成的更优化的方式迭代某些源。通过创建这些自定义迭代器,他们可以将有关源的额外信息传递给单个链中的后续 LINQ 操作(而不是使该信息仅可用于链中的第一个操作)。因此,所有WhereSelect 操作(它们之间没有其他任何东西)可以作为一个执行。

【讨论】:

如果您检查对我的问题的编辑,您会看到索引集合解释了自定义分叉但不一定自己实现迭代器,我只是惊讶于引入了如此多的编译器魔法来支持 linq 并且'不在实现中使用它 然而github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/… 似乎让你正确地将选择器与 where 结合起来。虽然我不明白为什么必须实现迭代器而不是简单地传递给需要 2 个资金并且也会产生的 whereselect,但我真的不明白为什么他们手动执行迭代器工作 @RonanThibaudau 让我澄清一下。如果对数组执行 LINQ 操作,则可以利用其索引器。但是,如果您对该结果执行另一个操作(并假设结果现在是“通用”迭代器),那么下一个操作将失去利用索引器的能力(因为就第二个操作而言,源现在是一个“通用”可枚举)。通过创建自定义迭代器,他们允许多个 LINQ 操作利用索引器(而不是只允许它用于链中的第一个操作)。现在更有意义了吗? 实际上是的,这是有道理的,如果你只是让出而不是返回迭代器,那么链接到另一个方法将链接到 IEnumerable 版本(就像现在一样),但没有办法他们要检查的是“whateveriterator”,因为这将是一个自动生成的类型,这对于他们为什么手动执行它实际上非常有意义。将其作为答案 是的。顺便说一句,yield 的编译器魔法并没有被引入来支持 linq,它从 2.0 开始就在 .NET 中,这比 linq 早了一段时间。

以上是关于为啥 linq to object 手动实现迭代器?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以为 linq-to-objects 编译查询

Linq之旅:Linq入门详解(Linq to Objects)

Linq之旅:Linq入门详解(Linq to Objects)

如何轻松地重新排序 LINQ to SQL 设计器中的列?

LINQ to Object——延时执行的Enumerable类方法

LINQ to Object - 如何为子组实现 WHERE 子句“如果至少有一个元素是”