2016-08-25 61 views
27

我有跳过n行代码工作,从使用File.ReadLines给定的文件y线,SkipTake组合功能。当我尝试打开由filePath下一次给定的文件:IEnumerable.Take(0)上File.ReadLines似乎不转让/关闭文件句柄

string[] Lines = File.ReadLines(filePath).Skip(0).Take(0).ToArray(); 
using (StreamWriter streamWriter = new StreamWriter(filePath)) 
{ 
    // ... 
} 

我上“using”行File in use by another process例外。

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

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

+3

你绝对*。*我的'ToArray'打电话?我希望处理迭代器,它应该适当地工作。你能提供一个[mcve]吗? (我希望你能用'Main'方法显示这一切。) –

+0

是的,正确的,它被调用。记住它不是在File.ReadLines IEnumerable上调用的,而是在Take(0)返回的IEnumerable中 – Titus

+0

是的,但我期望最终处理原始迭代器。 (使用你自己的迭代器方法证明是相当容易的。) –

回答

39

这基本上是File.ReadLines中的一个错误,而不是TakeReadLines返回一个IEnumerable<T>,这在逻辑上应该是懒惰的,但热切地打开文件。除非实际遍历返回值,否则没有任何可处置的内容。

这是破坏只反复一次。例如,您应该可以编写:

var lines = File.ReadLines("text.txt"); 
var query = from line1 in lines 
      from line2 in lines 
      select line1 + line2; 

...应该给出文件中行的交叉积。它不是,由于破碎。

File.ReadLines应该来实现这样的事:

public static IEnumerable<string> ReadLines(string filename) 
{ 
    return ReadLines(() => File.OpenText(filename)); 
} 

private static IEnumerable<string> ReadLines(Func<TextReader> readerProvider) 
{ 
    using (var reader = readerProvider()) 
    { 
     string line; 
     while ((line = reader.ReadLine()) != null) 
     { 
      yield return line; 
     } 
    } 
} 

可惜这不是:(

选项:

  • 使用上述的File.ReadLines
  • ,而不是写你的自己实施Take其中总是开始迭代,例如,

    public static IEnumerable<T> Take<T>(this IEnumerable<T> source, int count) 
    { 
        // TODO: Argument validation 
        using (var iterator = source.GetEnumerator()) 
        { 
         while (count > 0 && iterator.MoveNext()) 
         { 
          count--; 
          yield return iterator.Current; 
         } 
        } 
    } 
    
+6

“'File.ReadLines' _应该像这样实现”像老板。 –

+7

没有比Jon Skeet出现并告诉你BCL实现是疯狂的,而你的代码不是。 – Sabre

18

从上面File.ReadLines() in the Reference Source的评论,显然是负责的团队知道这个“错误”:

不能变更为保持与4.0兼容的已知问题:

  • 底层StreamReader预先分配给IEnumerable<T>之前 GetEnumerator甚至被称为。虽然这是在例外,如 DirectoryNotFoundExceptionFileNotFoundExceptionFile.ReadLines(用户可能希望)直接扔好,这也意味着,读者 将在枚举(被泄露,如果用户没有真正的foreach的,因此 调用Dispose on至少一个IEnumerator<T>实例)

因此他们想File.ReadLines()时传递了无效或无法读取路径,立即扔掉,而不是枚举时抛出。

另一种方法很简单:不要拨打Take(0),或者如果您对其内容没有真正的兴趣,请不要完全阅读文件。

-1

在我看来,根本原因是Enumerable.Take迭代如果count为零,不处置的底层迭代器,因为代码不进入foreach环 - 见referencesource。 如果一个人在修改下列方式发行得到解决代码:

static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count) 
{ 
    foreach (TSource element in source) 
    { 
     if (--count < 0) break; 
     yield return element; 
    } 
} 
+2

声称'Take'是因为没有处理一个避免创建的对象而犯的错误,这并没有太大意义。 – hvd

+1

确实。没有东西应该*调用GetEnumerator并处理结果。 –