備註
本內容經Pearson Education, Inc.許可,從《框架設計指導方針:可重用 .NET 程式庫的慣例、習用語和模式(第2版)》轉載。 該版於2008年出版,該書自那以後已於 第三版全面修訂。 此頁面的某些資訊可能已過期。
所有程式都會在執行期間取得一或多個系統資源,例如記憶體、系統句柄或資料庫連線。 開發人員在使用這類系統資源時必須小心,因為它們必須在取得和使用之後釋放。
CLR 支援自動記憶體管理。 受控記憶體(使用 C# 運算元 new配置的記憶體)不需要明確釋放。 它由垃圾收集器 (GC) 自動釋放。 這可讓開發人員擺脫釋放記憶體的乏味和困難工作,而且一直是 .NET Framework 提供前所未有的生產力的主要原因之一。
不幸的是,受控記憶體只是許多類型的系統資源之一。 受控記憶體以外的資源仍然需要明確釋放,並稱為非受控資源。 GC 特別不是設計來管理這類非受控資源,這表示管理非受控資源的責任在於開發人員手中。
CLR 提供一些協助以釋放非受控資源。 System.Object 宣告一個虛擬方法 Finalize(也稱為完成項),這個方法在物件記憶體由 GC 回收之前由 GC 呼叫,可以被覆寫以釋放非受控資源。 覆寫完成項的類型稱為可完成的類型。
雖然終結器在某些清理情境中很有效,但它們有兩個重大缺點:
當 GC 偵測到物件符合集合資格時,就會呼叫完成項。 這在不再需要資源之後,會在某個不確定的一段時間內發生。 開發人員想要釋放資源的時間與終結器實際釋放資源的時間之間的延遲,在需要獲取許多稀缺資源(很容易耗盡的資源)或持續使用資源成本高昂(例如大型 unmanaged 記憶體緩衝區)的情況下,可能會無法接受。
當 CLR 需要呼叫終結器時,它必須延後物件記憶體的回收,直到下一輪垃圾回收為止(終結器會在垃圾回收之間執行)。 這表示物件的記憶體(及其參考的所有物件)在較長時間內不會被釋放。
因此,在很多案例中,完全依賴完成項可能不適合,因為必須儘快回收非受控資源、處理稀缺資源時,或處理高效能的案例,而最終化增加的 GC 額外負荷是無法接受的。
架構提供 System.IDisposable 介面供開發人員實作,讓他們能及時手動釋放不再需要的非受控資源。 它還提供 GC.SuppressFinalize 方法,讓 GC 知道物件已經被手動處置,因此不需要再進行終結。這樣的情況下,物件的記憶體可以更早回收。 實作 IDisposable 介面的類型稱為可處置型別。
Dispose 模式旨在標準化終結器和介面的使用方式及IDisposable的實作。
模式的主要動機是減少 Finalize 和 Dispose 方法實作的複雜性。 複雜性源於方法共用部分但並非所有程式代碼路徑的事實(章節稍後會說明差異)。 此外,一些與語言支援中決定性資源管理演變相關的模式元素也有其歷史原因。
✓ DO 在包含可處置型別實例的類型上實作基本處置模式。 如需基本模式的詳細資訊,請參閱 基本處置 模式一節。
如果類型負責其他可處置物件的存留期,開發人員也需要方法來處置它們。 使用容器的 Dispose 方法是一種方便的方式,可讓這種情況成為可能。
✓ DO 實作基本處置模式,並在持有需要明確釋放且沒有終結器的資源的類型上提供終結器。
例如,應該在儲存非受控記憶體緩衝區的類型上實作模式。 [可完成的類型] 區段討論與實作完成項相關的指導方針。
✓ 請考慮 在自身不包含非託管資源或可處置物件的類別上實施基本處置模式,尤其當這些類別很可能有包含此類資源或物件的子類型時。
這是一個絕佳的 System.IO.Stream 類別範例。 雖然它是不保存任何資源的抽象基類,但其大部分子類別都會這麼做,因此會實作此模式。
基本處置模式
模式的基本實作牽涉到實作System.IDisposable介面,並宣告Dispose(bool)方法,以實現Dispose方法與可選擇的終結器之間需要共用的所有資源清除邏輯。
下列範例示範基本模式的簡單實作:
public class DisposableResourceHolder : IDisposable {
private SafeHandle resource; // handle to a resource
public DisposableResourceHolder() {
this.resource = ... // allocates the resource
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (resource!= null) resource.Dispose();
}
}
}
布林參數 disposing 指出是否是從 IDisposable.Dispose 實作或終結器呼叫該方法。 實作 Dispose(bool) 應該先檢查 參數,再存取其他參考物件(例如上述範例中的資源字段)。 只有在從IDisposable.Dispose實作呼叫方法時(disposing 參數等於 true),才應該存取這類物件。 如果從完成項叫用 方法 (disposing 為 false),則不應該存取其他物件。 原因是物件會以無法預測的順序完成,因此它們或其任何相依性可能已經完成。
此外,本節也適用於基底尚未實作 Dispose 模式的類別。 如果您繼承自已實作模式的類別,只要覆寫 Dispose(bool) 方法以提供額外的資源清除邏輯即可。
✓ DO 宣告 protected virtual void Dispose(bool disposing) 方法,以集中釋放非受控資源的相關所有邏輯。
所有資源清除都應該發生在此方法中。 從終結器和 IDisposable.Dispose 方法呼叫此方法。 如果從終結器內叫用,參數將會是 false。 它應該用來確保在完成期間執行的任何程式代碼都不會存取其他可完成的物件。 下一節將說明實作終結器的詳細說明。
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (resource!= null) resource.Dispose();
}
}
✓ DO 只要呼叫 IDisposable 後面接著 Dispose(true),即可實作 GC.SuppressFinalize(this) 介面。
只有在SuppressFinalize成功執行後,才應呼叫Dispose(true)。
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
X DO NOT 將無參數的 Dispose 方法設為虛擬化。
Dispose(bool) 方法是應由子類別覆寫的方法。
// bad design
public class DisposableResourceHolder : IDisposable {
public virtual void Dispose() { ... }
protected virtual void Dispose(bool disposing) { ... }
}
// good design
public class DisposableResourceHolder : IDisposable {
public void Dispose() { ... }
protected virtual void Dispose(bool disposing) { ... }
}
X DO NOT 不宣告任何Dispose或Dispose()以外的Dispose(bool)方法多載。
Dispose 應該視為保留字,以協助編纂此模式,並防止實作者、用戶和編譯程式之間的混淆。 某些語言可能會選擇在特定類型上自動實作此模式。
✓ DO 允許 Dispose(bool) 呼叫方法多次。 方法可能會在第一次呼叫之後選擇不執行任何動作。
public class DisposableResourceHolder : IDisposable {
bool disposed = false;
protected virtual void Dispose(bool disposing) {
if (disposed) return;
// cleanup
...
disposed = true;
}
}
X AVOID 在內部拋出例外狀況,除非在處理程序遭受損毀的緊急情況下(如洩漏、不一致的共享狀態等)。
用戶預期對 Dispose 的呼叫不會引發例外狀況。
如果 Dispose 可能會引發例外狀況,則不會執行進一步的區塊清除邏輯。 若要解決此問題,用戶必須將每次呼叫 Dispose 的動作包裝在 try 區塊中(final 區塊內),這會導致非常複雜的清理處理程序。 如果執行 Dispose(bool disposing) 方法,而處置為 false 時,絕對不要拋出例外狀況。 在終結器上下文中執行時,這樣做將會終止該過程。
✓ DO 從處置後無法使用的任何成員拋出 ObjectDisposedException。
public class DisposableResourceHolder : IDisposable {
bool disposed = false;
SafeHandle resource; // handle to a resource
public void DoSomething() {
if (disposed) throw new ObjectDisposedException(...);
// now call some native methods using the resource
...
}
protected virtual void Dispose(bool disposing) {
if (disposed) return;
// cleanup
...
disposed = true;
}
}
✓ 考慮 提供 方法 Close(),除了 Dispose(),如果「close」是本地區的標準術語。
這樣做時,請務必讓 Close 的實作與 Dispose 相同,並考慮明確地實作 IDisposable.Dispose 方法。
public class Stream : IDisposable {
IDisposable.Dispose() {
Close();
}
public void Close() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
可完成的類型
可終結的類型是透過覆寫終結器並在 Dispose(bool) 方法中提供終結程序的程式碼路徑來擴充基本處置模式的類型。
完成項顯然難以正確實作,主要是因為您無法在執行期間對系統狀態做出某些(通常有效的)假設。 應仔細考慮下列指導方針。
請注意,某些指導方針不僅適用於 Finalize 方法,也適用於任何從終止器調用的代碼。 在先前定義的基本處置模式案例中,這表示當Dispose(bool disposing)參數為 false 時,邏輯執行於disposing內部。
如果基類已經可完成並實作基本處置模式,則不應該再次覆寫 Finalize 。 您應該改為覆寫 Dispose(bool) 方法,以提供額外的資源清除邏輯。
下列程式代碼顯示可完成類型的範例:
public class ComplexResourceHolder : IDisposable {
private IntPtr buffer; // unmanaged memory buffer
private SafeHandle resource; // disposable handle to a resource
public ComplexResourceHolder() {
this.buffer = ... // allocates memory
this.resource = ... // allocates the resource
}
protected virtual void Dispose(bool disposing) {
ReleaseBuffer(buffer); // release unmanaged memory
if (disposing) { // release other disposable objects
if (resource!= null) resource.Dispose();
}
}
~ComplexResourceHolder() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
X AVOID 讓類型成為可完成的。
請仔細考慮任何您認為需要終結器的情況。 從效能和程式碼複雜度的觀點來看,使用具備終結器的實例會產生實際的成本。 偏好使用資源包裝器,例如 SafeHandle 來盡可能封裝未管理資源,在這種情況下,終結器就變得不必要,因為包裝器會負責自行清理資源。
X 不應將 實值型別設為可終止。
只有參考型別實際上會由 CLR 終結,因此會忽略任何為實值型別設置終結器的嘗試。 C# 和C++編譯程式會強制執行此規則。
✓ 確保 類型可以被完成,特別是當它負責釋放沒有自身完成器的非管理資源時。
在實作終結器時,只需呼叫Dispose(false)並將所有資源清除邏輯放在Dispose(bool disposing)方法內即可。
public class ComplexResourceHolder : IDisposable {
~ComplexResourceHolder() {
Dispose(false);
}
protected virtual void Dispose(bool disposing) {
...
}
}
✓ DO 會在每個可完成的類型上實作基本處置模式。
這讓使用者可以用一種方法來明確執行那些由終結器負責管理的相同資源的確定性清理。
X DO NOT 存取終結器程式碼路徑中的任何可以被終結的物件,因為它們很可能已經被終結。
例如,具有另一個可終結物件 B 的引用的可終結物件 A,無法可靠地在 A 的終結器中使用 B,反之亦然。 終結器會以隨機順序呼叫(缺少關鍵終結的弱排序保證)。
此外,請注意,儲存在靜態變數中的物件會在應用程式域卸載期間或結束進程時,於特定點收集。 存取參考可終結的物件的靜態變數(或呼叫可能會使用儲存在靜態變數中的值的靜態方法),如果 Environment.HasShutdownStarted 傳回 true,這可能會有不安全的風險。
✓ 請確定 您的 Finalize 方法受到保護。
C#、C++和 VB.NET 開發人員不需要擔心這個問題,因為編譯程式可協助強制執行此指導方針。
X DO NOT 讓例外狀況從完成項邏輯逸出,但系統關鍵性失敗除外。
如果終結器擲回例外狀況,CLR 將會關閉整個程序(從 .NET Framework 2.0 版開始),阻止其他終結器的執行,並導致資源無法被有序地釋放。
✓ 請考慮 建立和使用重要的可終結物件(包含類型階層 CriticalFinalizerObject的類型),當終結器絕對必須在強制應用程式域卸載和執行緒中止的情況下執行。
© 2005年、2009年Microsoft公司部分。 保留所有權利。
經 Pearson Education, Inc. 許可重新刊登自 Krzysztof Cwalina 和 Brad Abrams 所著的 架構設計指導方針: 可重複使用的 .NET 程式庫慣例、慣用語和模式,第 2 版,2008 年 10 月 22 日由 Addison-Wesley Professional 發行,作為 Microsoft Windows 開發系列的一部分。