奧萊特處置的爭論,這裏有雲的好一段壞的代碼:的迭代器塊
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 */
}
}
現在有些情況下進入問題:
CachingProxyList
是IEnumerable<T>
的實現,提供了一個自定義的「緩存」枚舉器:它的構造函數需要IEnumerable<T>
,並且最初通過它進行枚舉,但將每個項目保存在私有的List<T>
字段中,以便在繼續進行實際解析之前進一步進行迭代(而不是一次又一次地解析;或者不得不解析一個巨大的日誌ju st來查詢它的一小部分)。
請注意,這種優化實際上是需要,它的大部分已經工作(如果我刪除了using
語句,一切都很好,除了泄漏的文件句柄)。
Parse
和Tokenize
都是迭代器塊(AFAIK,唯一可以同時推遲執行和清理代碼的方法);他們的簽名是IEnumerable<Event> Parse(IEnumerable<Token>)
和IEnumerable<Token> Tokenize(StreamReader)
。他們的邏輯與這個問題無關。
邏輯流程非常清晰;代碼的每個部分的意圖都很明顯;但那些using
塊與整個延遲執行的事情沒有相處(當我通過我的Log
對象枚舉時,using
已經退出並且流被丟棄,所以Tokenize
試圖從其中讀取悲慘崩潰)。
我可以在文件(開放流)上鎖定相對較長的時間,但遲早我不得不關閉它。由於我無法真正使用using
,我必須明確處理這些流。
問題是:我應該在哪裏撥打電話Dispose()
?有沒有什麼常見的成語來處理這些情景?我不希望這樣做是「舊的方式」(在幾個地方發佈資源,每次發佈時都要仔細檢查每個發佈版本,等等)。
我的第一個想法是使Log
類一次性的,所以它的構造可以採取一個文件名,並有類內的所有資源管理(只要求消費者在完成時處置Log
本身的),但我在調用base
構造函數之前無法創建並保存流(該流是產生該構造函數參數的調用所必需的)。
注意:除非嚴格需要,否則不應觸及CachingProxyList
(我想保持足夠的通用性以使其可重用)。特別是,構造函數對於強制執行某些不變量是必不可少的,其餘的實現嚴重依賴於其中(例如內部枚舉器對象永遠不爲null)。其他一切,OTOH,應該是公平的遊戲。
感謝您的耐心,如果您已閱讀本文,並且也提前感謝您提供的任何幫助;)
。
這是微妙的,但導致了一個解決方案:它將問題轉化爲決定誰應該擁有這些流,並且我猜'Tokenize'方法是最好的候選者(畢竟它是唯一真正使用它的人)。 「所有者負責在資源上調用Dispose()」明確回答了問題,並且「每個資源都必須有一個所有者」,這使問題的根源顯而易見。謝謝! – 2011-04-05 23:56:51
關於編輯的一個注意事項:正如我在問題中提到的那樣,使Log類是一次性是我的第一個想法,但有一些問題(將流傳遞給'Tokenize',但也保存以備後用)。實際的解決方案是將文件名本身一直傳遞給'Tokenize',然後該方法將創建「擁有」,並處理這些流。在一個側面說明中,我認爲突出「一個所有者」的觀點是一個好主意:解決這個問題一直是關鍵。 – 2011-04-06 00:13:12
+1。 herenvardo,如果流(或任何其他類似的資源)由其他東西所擁有,則可以不在開放流附近「使用」。將文件名傳遞給您的Log類使其負責同時處理兩件事 - 打開文件和讀取日誌,考慮實際將流傳遞給拆分責任。至少考慮有兩種風味(檢查可能的方法XmlWriter.Create方法)。 – 2011-04-06 01:22:31