為什麼需要標準模式?
- 確保**非受控資源** (檔案控制代碼、資料庫連線、GDI 物件、COM、記憶體指標) **一定被釋放**。
- 提供**決定性釋放** (
Dispose()) 供使用者主動呼叫,提升效能。
- 提供**防禦性最終防線** (
Finalize) 防止使用者忘記呼叫 Dispose()。
- 支援**衍生類別**正確鏈式釋放資源。
- 避免
Finalize 執行緒額外負擔 (GC.SuppressFinalize)。
標準模式三大核心成員
| 成員 | 存取層級 | 職責 |
public void Dispose() | public | 使用者呼叫;釋放受控+非受控資源;呼叫 GC.SuppressFinalize(this) |
protected virtual void Dispose(bool disposing) | protected virtual | 核心邏輯:disposing=true 釋放受控+非受控;false 僅釋放非受控;供衍生類別覆寫 |
~ClassName() (Finalize) | 無 (編譯器產生) | 呼叫 Dispose(false);僅作為安全網 |
基底類別完整實作
public class ResourceHolder : IDisposable
{
// 狀態旗標
private bool _disposed = false;
// 受控資源範例
private Stream _stream = null;
private EventHandler _eventHandler = null;
// 非受控資源範例
private IntPtr _handle = IntPtr.Zero; // 檔案控制代碼、GDI 物件等
// 1. 公開 Dispose - IDisposable 介面
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 關鍵:告知 GC 不需再呼叫 Finalize
}
// 2. 核心虛擬方法 - 衍生類別覆寫點
protected virtual void Dispose(bool disposing)
{
if (_disposed) return; // 防禦性:避免重複釋放
if (disposing)
{
// ---- 釋放受控資源 ----
_stream?.Dispose();
_stream = null;
// 取消事件訂閱 (防止記憶體洩漏)
if (_eventHandler != null)
{
SomeEventSource.Event -= _eventHandler;
_eventHandler = null;
}
}
// ---- 釋放非受控資源 (無論 disposing) ----
if (_handle != IntPtr.Zero)
{
CloseHandle(_handle); // P/Invoke
_handle = IntPtr.Zero;
}
_disposed = true;
}
// 3. Finalize - 防禦性最終防線
~ResourceHolder()
{
Dispose(false); // 僅清理非受控資源
}
// 公開方法範例:檢查 disposed 狀態
public void DoWork()
{
if (_disposed) throw new ObjectDisposedException(nameof(ResourceHolder));
// 實際工作...
}
// P/Invoke 範例
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
}
衍生類別正確覆寫模式
public class DerivedResource : ResourceHolder
{
private bool _disposed = false;
private AnotherManaged _child = new AnotherManaged();
private IntPtr _ownHandle = IntPtr.Zero;
protected override void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 1. 釋放自身受控資源
_child?.Dispose();
_child = null;
}
// 2. 釋放自身非受控資源
if (_ownHandle != IntPtr.Zero)
{
CloseOwnHandle(_ownHandle);
_ownHandle = IntPtr.Zero;
}
// 3. 呼叫基底類別 Dispose (關鍵!)
base.Dispose(disposing);
_disposed = true;
}
// 衍生類別若引入新非受控資源,同樣需要 Finalize
// 但因基底已有 Finalize 呼叫 Dispose(false),此處無需再寫析構函式
// 若衍生類別為 sealed,可移除 virtual、base.Dispose 呼叫
}
使用者端正確用法
// 方式 1: using 區塊 (推薦,編譯器產生 try/finally)
using (var res = new ResourceHolder())
{
res.DoWork();
} // 自動呼叫 Dispose()
// 方式 2: 手動 try/finally
var res = new ResourceHolder();
try { res.DoWork(); }
finally { if (res != null) res.Dispose(); }
關鍵規則速查表
- 實作
IDisposable 的類別**必須**實作完整模式 (含 Finalize),若持有非受控資源。
- 僅持有受控
IDisposable 成員時,**可省略 Finalize**,但仍建議實作完整模式以利衍生。
Dispose(bool) 中:disposing=true → 受控+非受控;false → 僅非受控。
- 衍生類別覆寫
Dispose(bool) **必須**呼叫 base.Dispose(disposing)。
GC.SuppressFinalize(this) 僅在 Dispose() (即 disposing=true 路徑) 呼叫。
- 所有公開方法開頭檢查
_disposed 拋 ObjectDisposedException。
Finalize 中**絕不**存取其他受控物件 (可能已終結)。
- 密封類別 (
sealed) 可簡化:移除 virtual、protected,直接實作 Dispose() 邏輯。
常見錯誤與後果
| 錯誤 | 後果 |
忘記 GC.SuppressFinalize | 物件進入 Finalizer Queue,延遲記憶體回收,增加 GC 壓力 |
| Finalize 中呼叫受控物件方法 | 非決定性錯誤、NullReferenceException、資料損毀 |
衍生類別未呼叫 base.Dispose | 基底非受控資源洩漏 |
| 重複 Dispose 未防禦 | 二次釋放導致例外或資料損毀 |
公開方法未檢查 _disposed | 物件被釋放後仍可呼叫方法,行為未定義 |