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()
时,它将创建原始对象的克隆,并完成disposesGetEnumerator()
。然后它尝试从该读取器读取,并抛出异常StreamReader
- 当
调用First()
时,返回原始引用,并使用open reader完成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;
}
}
}
}
使用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()
),然后读取,然后处理。