为什么IEumerator 会影响IEnumerable 的状态,即使枚举器从未到达终点?

我很好奇为什么以下内容在“最后”赋值时抛出错误消息(文本阅读器关闭exception):

IEnumerable textRows = File.ReadLines(sourceTextFileName); IEnumerator textEnumerator = textRows.GetEnumerator(); string first = textRows.First(); string last = textRows.Last(); 

但是以下执行正常:

 IEnumerable textRows = File.ReadLines(sourceTextFileName); string first = textRows.First(); string last = textRows.Last(); IEnumerator textEnumerator = textRows.GetEnumerator(); 

不同行为的原因是什么?

据我所知,你在框架中发现了一个错误。 由于一些事物的相互作用,这是相当微妙的:

  • 当您调用ReadLines() ,实际上会打开该文件。 就个人而言,我认为这本身就是一个错误; 我希望并希望它是懒惰的 – 只有当你尝试开始迭代它时才打开文件。
  • 当您一次在ReadLines的返回值上调用GetEnumerator()时,它实际上将返回相同的引用。
  • First()调用GetEnumerator() ,它将创建一个克隆。 这将与textEnumerator共享相同的StreamReader
  • First()处理它的克隆时,它将处理StreamReader ,并将变量设置为null 。 这不会影响原始变量,现在引用已经处理的StreamReader
  • Last()调用GetEnumerator() ,它将创建原始对象的克隆,并配置StreamReader 。 然后它尝试从该读取器读取,并抛出exception。

现在将其与第二个版本进行比较:

  • First()调用GetEnumerator() ,将返回原始引用,并使用打开的阅读器完成。
  • First()然后调用Dispose() ,将释放阅读器并将变量设置为null
  • Last()调用GetEnumerator() ,将创建一个克隆 – 但由于它的克隆值具有null引用,因此创建了一个新的StreamReader ,因此它能够StreamReader地读取该文件。 然后它处理克隆,关闭阅读器
  • 当调用GetEnumerator() ,原始对象的第二个克隆,打开另一个StreamReader – 再次,没有问题。

所以基本上,第一个片段中的问题是你第二次调用GetEnumerator() (在First() )而没有丢弃第一个对象。

这是同一问题的另一个例子:

 using System; using System.IO; using System.Linq; class Test { static void Main() { var lines = File.ReadLines("test.txt"); var query = from x in lines from y in lines select x + "/" + y; foreach (var line in query) { Console.WriteLine(line); } } } 

你可以通过调用File.ReadLines两次来解决这个问题 – 或者使用一个真正懒惰的ReadLines实现,如下所示:

 using System.IO; using System.Linq; class Test { static void Main() { var lines = ReadLines("test.txt"); var query = from x in lines from y in lines select x + "/" + y; foreach (var line in query) { Console.WriteLine(line); } } static IEnumerable ReadLines(string file) { using (var reader = File.OpenText(file)) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } } } 

在后面的代码中,每次调用GetEnumerator()时都会打开一个新的StreamReader – 因此结果是test.txt中的每对行。