File_adLines上的IEnumerable.Take(0)似乎没有处理/关闭File句柄

我有一个函数,它跳过n行代码并使用带有SkipTake组合的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 processexceptionFile in use by another processFile in use by another process

它看起来像IEnumerable.Take(0)是罪魁祸首,因为它返回一个空的IEnumerable而不枚举File.ReadLines()返回的对象,我相信它不会处理该文件。

我对吗? 他们不应该枚举以避免这种错误吗? 如何正确地做到这一点?

这基本上是File.ReadLines一个错误,而不是TakeReadLines返回一个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 (用户可能期望)会直接抛出DirectoryNotFoundExceptionFileNotFoundException类的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; } }