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



我很好奇为什么以下内容会在"最后"分配上传达错误消息(文本读取器封闭异常):

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()时,它将创建一个克隆。这将与textEnumerator共享相同的StreamReader
  • First()处置其克隆时,它将处置StreamReader,并将其设置为变量 null。这不会影响原始中的变量,该变量现在是指disted StreamReader
  • Last()调用GetEnumerator()时,它将创建原始对象的克隆,并配有DESPOSES 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;
            }
        }
    }
}

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

相关内容

  • 没有找到相关文章