2016-08-25 56 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並處理結果。 –