这段代码:
IEnumerable<string> lines = File.ReadLines("file path");
foreach (var line in lines)
{
Console.WriteLine(line);
}
foreach (var line in lines)
{
Console.WriteLine(line);
}
抛出一个 ObjectDisposedException : {"Cannot read from a closed TextReader."}
如果第二个 foreach
被执行。
似乎从中返回了迭代器对象 File.ReadLines(..)
不能一次列举。您必须通过调用获取新的迭代器对象 File.ReadLines(..)
然后用它来迭代。
如果我更换 File.ReadLines(..)
与我的版本(参数未经验证,这只是一个例子):
public static IEnumerable<string> MyReadLines(string path)
{
using (var stream = new TextReader(path))
{
string line;
while ((line = stream.ReadLine()) != null)
{
yield return line;
}
}
}
可以多次迭代文件的行。
使用调查 .Net Reflector
表明执行了 File.ReadLines(..)
叫私人 File.InternalReadLines(TextReader reader)
创建实际的迭代器。作为参数传递的阅读器用于 MoveNext()
迭代器的方法来获取文件的行,并在我们到达文件末尾时处理。这意味着一次 MoveNext()
返回false没有办法再次迭代,因为读取器已关闭,你必须通过创建一个新的迭代器来获得一个新的读取器 ReadLines(..)
method.In我的版本中创建了一个新的阅读器 MoveNext()
每次我们开始一个新的迭代时的方法。
这是预期的行为吗? File.ReadLines(..)
方法?
我发现在你枚举结果之前每次调用方法都很麻烦。每次迭代使用该方法的Linq查询的结果之前,您还必须调用该方法。
我知道这是旧的,但实际上我在Windows 7机器上处理一些代码时遇到了这个问题。与人们在这里所说的相反,这实际上就是这样 是 一个bug。看到 这个链接。
因此,简单的解决方法是更新您的.net framefork。我认为这值得更新,因为这是最热门的搜索结果。
我不认为这是一个错误,我不认为这是不寻常的 - 事实上,这是我期望的文本文件阅读器之类的东西。 IO是一项昂贵的操作,因此通常您希望一次性完成所有操作。
这不是一个bug。但我相信你可以使用ReadAllLines()来做你想做的事情。 ReadAllLines创建一个字符串数组并将所有行拉入数组,而不是像ReadLines那样只是一个简单的枚举器。
如果你需要两次访问这些行,你可以随时将它们缓冲到一个 List<T>
using System.Linq;
List<string> lines = File.ReadLines("file path").ToList();
foreach (var line in lines)
{
Console.WriteLine(line);
}
foreach (var line in lines)
{
Console.WriteLine(line);
}
我不知道它是否可以被认为是一个错误,如果它是设计但我可以说两件事......
- 这应该发布在Connect上,而不是StackOverflow,尽管它们在4.0发布之前不会改变它。这通常意味着他们永远无法修复它。
- 该方法的设计肯定存在缺陷。
你是正确的指出返回一个IEnumerable意味着它应该是可重用的,如果迭代两次它不保证相同的结果。如果它返回了IEnumerator,那么它将是一个不同的故事。
所以无论如何,我认为这是一个很好的发现,我认为API是一个糟糕的开始。 ReadAllLines和ReadAllText为您提供了获取整个文件的一种非常方便的方法,但是如果调用者对使用惰性枚举的性能足够关注,那么他们不应该首先将这么多的责任委托给静态帮助器方法。
我相信你将IQueryable与IEnumerable混淆了。是的,IQueryable可以被视为IEnumerable,但它们并不完全相同。每次使用IQueryable查询,而IEnumerable没有这样隐含的重用。
Linq查询返回IQueryable。 ReadLines返回一个IEnumerable。
这里有一个微妙的区别,因为创建了枚举器的方式。当你在它上面调用GetEnumerator()时,IQueryable会创建一个IEnumerator(由foreach自动完成)。 ReadLines()在调用ReadLines()函数时创建IEnumerator。因此,当您重用IQueryable时,它会在您重用它时创建一个新的IEnumerator,但由于ReadLines()创建了IEnumerator(而不是IQueryable),因此获取新IEnumerator的唯一方法是再次调用ReadLines() 。
换句话说,您应该只能期望重用IQueryable而不是IEnumerator。
编辑:
在进一步思考(没有双关语)我认为我的初步反应有点过于简单化了。如果IEnumerable不可重用,则无法执行以下操作:
List<int> li = new List<int>() {1, 2, 3, 4};
IEnumerable<int> iei = li;
foreach (var i in iei) { Console.WriteLine(i); }
foreach (var i in iei) { Console.WriteLine(i); }
显然,人们不会指望第二个foreach失败。
这种抽象的问题往往是,并非一切都完美。例如,Streams通常是单向的,但对于网络使用,它们必须适应双向工作。
在这种情况下,最初设想IEnumerable是一个可重用的功能,但它已被改编为如此通用,以至于可重用性不是保证,甚至不应该是预期的。见证以不可重复使用的方式使用IEnumerables的各种库的爆炸式增长,例如Jeffery Richters PowerThreading库。
我根本不认为我们可以假设IEnumerables在所有情况下都可以重复使用。
这不是一个错误。 File.ReadLines()使用延迟评估,但不是 幂等。这就是为什么连续两次枚举它是不安全的。记住一个 IEnumerable
表示可以枚举的数据源,但它并未说明两次枚举是安全的,尽管这可能是意料之外的,因为大多数人习惯使用IEnumerable而不是幂等集合。
来自 MSDN:
ReadLines(String,System)和
ReadAllLines(String,System)方法
区别如下:当你使用时
ReadLines,你可以开始枚举
之前的字符串集合
整个收藏归还;当你
使用ReadAllLines,你必须等待
返回整个字符串数组
在你可以访问之前
array.Therefore,因此,当你工作
对于非常大的文件,ReadLines可以
更有效率。
您通过反射器的发现是正确的,并验证此行为。您提供的实现避免了这种意外行为,但仍然使用延迟评估。