2011-04-05 81 views
3

奧萊特處置的爭論,這裏有雲的好一段壞的代碼:的迭代器塊

public class Log : CachingProxyList<Event> { 
    public static Log FromFile(String fullPath) { 
     using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.Read)) { 
      using (StreamReader sr = new StreamReader(fs)) { 
       return new Log(sr); 
      } 
     } 
    } 
    public Log(StreamReader stream) 
     : base(Parser.Parse(Parser.Tokenize(stream))) { 
     /* Here goes some "magic", the whole reason for this 
     * class to exist, but not really relevant to the issue */ 
    } 
} 

現在有些情況下進入問題:

CachingProxyListIEnumerable<T>的實現,提供了一個自定義的「緩存」枚舉器:它的構造函數需要IEnumerable<T>,並且最初通過它進行枚舉,但將每個項目保存在私有的List<T>字段中,以便在繼續進行實際解析之前進一步進行迭代(而不是一次又一次地解析;或者不得不解析一個巨大的日誌ju st來查詢它的一小部分)。
請注意,這種優化實際上是需要,它的大部分已經工作(如果我刪除了using語句,一切都很好,除了泄漏的文件句柄)。

ParseTokenize都是迭代器塊(AFAIK,唯一可以同時推遲執行和清理代碼的方法);他們的簽名是IEnumerable<Event> Parse(IEnumerable<Token>)IEnumerable<Token> Tokenize(StreamReader)。他們的邏輯與這個問題無關。

邏輯流程非常清晰;代碼的每個部分的意圖都很明顯;但那些using塊與整個延遲執行的事情沒有相處(當我通過我的Log對象枚舉時,using已經退出並且流被丟棄,所以Tokenize試圖從其中讀取悲慘崩潰)。

我可以在文件(開放流)上鎖定相對較長的時間,但遲早我不得不關閉它。由於我無法真正使用using,我必須明確處理這些流。

問題是:我應該在哪裏撥打電話Dispose()?有沒有什麼常見的成語來處理這些情景?我不希望這樣做是「舊的方式」(在幾個地方發佈資源,每次發佈時都要仔細檢查每個發佈版本,等等)。

我的第一個想法是使Log類一次性的,所以它的構造可以採取一個文件名,並有類內的所有資源管理(只要求消費者在完成時處置Log本身的),但我在調用base構造函數之前無法創建並保存流(該流是產生該構造函數參數的調用所必需的)。

注意:除非嚴格需要,否則不應觸及CachingProxyList(我想保持足夠的通用性以使其可重用)。特別是,構造函數對於強制執行某些不變量是必不可少的,其餘的實現嚴重依賴於其中(例如內部枚舉器對象永遠不爲null)。其他一切,OTOH,應該是公平的遊戲。

感謝您的耐心,如果您已閱讀本文,並且也提前感謝您提供的任何幫助;)

回答

5
  • 封裝非託管資源的類需要實現配置模式(IDisposable接口)。例如流,數據庫連接等
  • 每個資源都必須有一個所有者
  • 業主負責對資源
  • 如果業主不能立即致電其資源Dispose()或者不知道什麼時候打電話叫Dispose()那麼它需要實現IDisposable接口本身,並在其中的資源上調用Dispose()

上述聲明可能有例外,但這是一般規則。例如StreamWriter需要一個流(實現IDisposable接口),並強制它實現IDisposable接口本身 - ,因爲它不知道何時處置它

你的情況是一樣的。你的班級使用一次性資源,但不知道何時處理它 - 或者這就是我所假設的。這將使它實現IDisposable接口。您的Log班級的客戶必須在班上撥打Dispose()

所以你可以看到,這成爲一個鏈,而非一次性的客戶將不得不調用Dispose的資源,它使用和資源將配置其資源,等等

+0

這是微妙的,但導致了一個解決方案:它將問題轉化爲決定誰應該擁有這些流,並且我猜'Tokenize'方法是最好的候選者(畢竟它是唯一真正使用它的人)。 「所有者負責在資源上調用Dispose()」明確回答了問題,並且「每個資源都必須有一個所有者」,這使問題的根源顯而易見。謝謝! – 2011-04-05 23:56:51

+0

關於編輯的一個注意事項:正如我在問題中提到的那樣,使Log類是一次性是我的第一個想法,但有一些問題(將流傳遞給'Tokenize',但也保存以備後用)。實際的解決方案是將文件名本身一直傳遞給'Tokenize',然後該方法將創建「擁有」,並處理這些流。在一個側面說明中,我認爲突出「一個所有者」的觀點是一個好主意:解決這個問題一直是關鍵。 – 2011-04-06 00:13:12

+0

+1。 herenvardo,如果流(或任何其他類似的資源)由其他東西所擁有,則可以不在開放流附近「使用」。將文件名傳遞給您的Log類使其負責同時處理兩件事 - 打開文件和讀取日誌,考慮實際將流傳遞給拆分責任。至少考慮有兩種風味(檢查可能的方法XmlWriter.Create方法)。 – 2011-04-06 01:22:31