为什么IEumerator <t>会影响IEnumerable <t>的状态,即使枚举器永远不会到达终点?</t> </t>

时间:2012-12-26 10:53:07

标签: c# ienumerable ienumerator textreader

我很好奇为什么以下内容会在“最后”作业中抛出错误消息(文本阅读器已关闭异常):

IEnumerable<string> textRows = File.ReadLines(sourceTextFileName);
IEnumerator<string> textEnumerator = textRows.GetEnumerator();

string first = textRows.First();
string last = textRows.Last();

但是以下表现不错:

IEnumerable<string> textRows = File.ReadLines(sourceTextFileName);

string first = textRows.First();
string last = textRows.Last();

IEnumerator<string> textEnumerator = textRows.GetEnumerator();

不同行为的原因是什么?

1 个答案:

答案 0 :(得分:12)

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

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

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

  • First()调用GetEnumerator()时,将返回原始引用,并以打开的阅读器完成。
  • First()然后调用Dispose()时,读者将被处理并且变量设置为null
  • Last()调用GetEnumerator()时,会创建一个克隆 - 但由于它克隆的值具有null引用,因此会创建一个新的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<string> ReadLines(string file)
    {
        using (var reader = File.OpenText(file))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }
}

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