C# 为什么iEmerator<;T>;影响IEnumerable的状态<;T>;就连统计员都没到尽头?

C# 为什么iEmerator<;T>;影响IEnumerable的状态<;T>;就连统计员都没到尽头?,c#,ienumerable,ienumerator,textreader,C#,Ienumerable,Ienumerator,Textreader,我很好奇为什么下面在“最后一次”赋值时抛出错误消息(文本读取器关闭异常): IEnumerable<string> textRows = File.ReadLines(sourceTextFileName); IEnumerator<string> textEnumerator = textRows.GetEnumerator(); string first = textRows.First(); string last = textRows.Last(); IEnu

我很好奇为什么下面在“最后一次”赋值时抛出错误消息(文本读取器关闭异常):

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

string first = textRows.First();
string last = textRows.Last();
IEnumerable textRows=File.ReadLines(sourceTextFileName);
IEnumerator textEnumerator=textRows.GetEnumerator();
string first=textRows.first();
字符串last=textRows.last();
但是,以下情况很好:

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

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

IEnumerator<string> textEnumerator = textRows.GetEnumerator();
IEnumerable textRows=File.ReadLines(sourceTextFileName);
string first=textRows.first();
字符串last=textRows.last();
IEnumerator textEnumerator=textRows.GetEnumerator();

不同行为的原因是什么

据我所知,您在框架中发现了一个bug。这是相当微妙的,因为一些事情的相互作用:

  • 调用
    ReadLines()
    时,文件实际上已打开。就我个人而言,我认为这本身就是一个缺陷;我期望并希望它是懒惰的——只在您尝试开始迭代文件时才打开它
  • 当您第一次调用
    ReadLines
    的返回值时,它实际上会返回相同的引用
  • First()
    调用
    GetEnumerator()
    时,它将创建一个克隆。这将与
    textEnumerator
  • First()
    处理其克隆时,它将处理
    StreamReader
    ,并将其变量设置为
    null
    。这不会影响原始文件中的变量,该文件现在引用已处理的
    StreamReader
  • Last()
    调用
    GetEnumerator()
    时,它将创建原始对象的克隆,并完成disposes
    StreamReader
    。然后它尝试从该读取器读取,并抛出异常
现在将其与第二个版本进行比较:

  • First()
    调用
    GetEnumerator()
    时,返回原始引用,并使用open reader完成
  • 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;
            }
        }
    }
}
使用System.IO;
使用System.Linq;
课堂测试
{
静态void Main()
{
变量行=读取行(“test.txt”);
var query=从行中的x开始
从y开始排队
选择x+“/”+y;
foreach(查询中的var行)
{
控制台写入线(行);
}
}
静态IEnumerable可读行(字符串文件)
{
使用(var reader=File.OpenText(文件))
{
弦线;
而((line=reader.ReadLine())!=null)
{
收益率回归线;
}
}
}
}

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

实际上,这两个代码都会在我的机器上崩溃…@digEmAll,第二个对我来说很好,当我试图确定文本文件中的最后一行时,第一个代码就中断了。@digEmAll:这很奇怪-第二个代码对我来说很好,我理解为什么它很好。你看到了什么问题,在哪里?@JonSkeet,第二个代码对我来说也失败了,同样的错误和同一行。@Andrei:Hmm。你使用的是哪个版本的.NET?这是否在调试器中?感谢您的详细解释。非常有趣,这促使我深入挖掘,因为我不知道First()或Last()处理内部迭代器/枚举器。@Freddy:所有使用
IEnumerator
的东西都应该处理它。我明白你的意思,但在本例中调用First()和Last()我希望它不会丢弃底层的文本阅读器。我仍然有点困惑,一方面必须手动处理,另一方面在调用First()或Last()之后,对象本身被处理。@JonSkeet:我刚刚看了一下.net 4.5源代码。他们用IEnumerator实现改变了基于产量的代码,试图保持相同的行为。无论如何,我已经在一个.net 4.0项目中复制了代码,第二个案例可以工作,而使用.net 4 File.ReadLines它不会…@Freddy:不,调用
First()
Last()
将创建(通过
GetEnumerator()
),然后读取,然后处理。