11

我很好奇为什么以下会在“最后”分配中引发错误消息(文本阅读器关闭异常):

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();

不同行为的原因是什么?

4

1 回答 1

12

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

  • 当您调用 时ReadLines(),该文件实际上已打开。就个人而言,我认为这本身就是一个错误。我希望并希望它会很懒惰-仅在您尝试开始对其进行迭代时才打开文件。
  • 当您GetEnumerator()一次调用的返回值时,它实际上将返回相同的引用。ReadLines
  • First()调用时GetEnumerator(),它将创建一个克隆。这将共享相同StreamReadertextEnumerator
  • First()处置其克隆时,它将处置StreamReader,并将变量设置为null。这不会影响原始变量中的变量,它现在指的是已处置的StreamReader
  • Last()调用时GetEnumerator(),它将创建原始对象的克隆,并带有 dispose StreamReader。然后它会尝试从该阅读器读取数据,并引发异常。

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

  • First()调用时GetEnumerator(),返回原始引用,并带有打开的阅读器。
  • First()then 调用Dispose()时,读取器将被释放并将变量设置为null
  • Last()调用时GetEnumerator(),将创建一个克隆 - 但因为它正在克隆的值有一个null引用,StreamReader所以会创建一个新的,因此它能够毫无问题地读取文件。然后它处理克隆,关闭阅读器
  • GetEnumerator()被调用时,原始对象的第二个克隆,打开另一个StreamReader- 再次,那里没有问题。

所以基本上,第一个片段中的问题是你在没有处理第一个对象的情况下GetEnumerator()第二次调用(in )。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 中的每一对行。

于 2012-12-26T11:11:32.840 回答