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

Mat*_*olf 11 c# ienumerable ienumerator textreader

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

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

string first = textRows.First();
string last = textRows.Last();
Run Code Online (Sandbox Code Playgroud)

但是以下执行正常:

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

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

IEnumerator<string> textEnumerator = textRows.GetEnumerator();
Run Code Online (Sandbox Code Playgroud)

不同行为的原因是什么?

Jon*_*eet 12

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

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

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

  • First()呼叫时GetEnumerator(),返回原始参考,完成打开的阅读器.
  • First()调用时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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以通过调用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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @Freddy:*使用`IEnumerator <T>`的所有*都应该处理它. (2认同)