我很好奇以下内容为何对“最后一个”分配抛出错误消息(文本阅读器关闭的异常):

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

出现不同行为的原因是什么?

最佳答案

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

  • 调用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中的每一对行。

    10-08 04:17