File_adLines上的IEnumerable.Take(0)似乎没有处理/关闭File句柄
我有一个函数,它跳过n
行代码并使用带有Skip
和Take
组合的File.ReadLines
从给定文件中获取y
行。 当我下次尝试打开filePath
给出的文件时:
string[] Lines = File.ReadLines(filePath).Skip(0).Take(0).ToArray(); using (StreamWriter streamWriter = new StreamWriter(filePath)) { // ... }
我在“ using
”行中获得了File in use by another process
exceptionFile in use by another process
的File in use by another process
。
它看起来像IEnumerable.Take(0)
是罪魁祸首,因为它返回一个空的IEnumerable
而不枚举File.ReadLines()
返回的对象,我相信它不会处理该文件。
我对吗? 他们不应该枚举以避免这种错误吗? 如何正确地做到这一点?
这基本上是File.ReadLines
一个错误,而不是Take
。 ReadLines
返回一个IEnumerable
,它在逻辑上应该是懒惰的,但它会急切地打开文件。 除非您实际迭代返回值,否则您无需处置任何内容。
它仅在迭代一次方面也被打破了。 例如,您应该能够写:
var lines = File.ReadLines("text.txt"); var query = from line1 in lines from line2 in lines select line1 + line2;
……应该在文件中提供行的交叉产品。 由于破碎,它没有。
File.ReadLines
应该实现如下:
public static IEnumerable ReadLines(string filename) { return ReadLines(() => File.OpenText(filename)); } private static IEnumerable ReadLines(Func readerProvider) { using (var reader = readerProvider()) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } }
不幸的是,它不是:(
选项:
- 使用上面而不是
File.ReadLines
-
编写自己的
Take
实现,它总是开始迭代,例如public static IEnumerable
Take (this IEnumerable source, int count) { // TODO: Argument validation using (var iterator = source.GetEnumerator()) { while (count > 0 && iterator.MoveNext()) { count--; yield return iterator.Current; } } }
从参考源中File.ReadLines()
上面的注释中可以看出,负责团队知道这个“bug”:
无法更改以保持与4.0兼容的已知问题:
- 在调用
GetEnumerator
之前,底层的StreamReader
是为IEnumerable
预先分配的。 虽然这很好,因为File.ReadLines
(用户可能期望)会直接抛出DirectoryNotFoundException
和FileNotFoundException
类的exception,但这也意味着如果用户实际上没有超过可枚举的那些,则读者将被泄露(因此调用Dispose至少在一个IEnumerator
实例上) 。
因此,当传递无效或不可读的路径时,他们希望File.ReadLines()
立即抛出,而不是在枚举时抛出。
替代方案很简单:如果您对其内容不感兴趣,则不要调用Take(0)
,或者不完全读取文件。
在我看来,根本原因是Enumerable.Take
如果count
为零,则迭代器不会处置底层迭代器,因为代码不会进入foreach
循环 – 请参阅referencesource 。 如果以下列方式修改代码,问题就会得到解决:
static IEnumerable TakeIterator (IEnumerable source, int count) { foreach (TSource element in source) { if (--count < 0) break; yield return element; } }