專案中大量類別實作 IDisposable,但許多開發者僅知「手動釋放資源」,不清其核心機制、與 GC 關係、托管/非托管資源差異。本文從接口定義、標準模式、記憶體分配真相三個維度深度剖析。
~Finalize())由 GC 執行緒非決定性呼叫,無法保證釋放時機,關鍵資源需決定性釋放。public class Foo : IDisposable
{
private bool _disposed = false;
// 公共 Dispose
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 告訴 GC 不需再呼叫終結器
}
// 核心虛方法,供衍生類別擴充
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// 釋放托管資源(其他 IDisposable 物件、事件訂閱等)
}
// 釋放非托管資源(CloseHandle、Marshal.FreeHGlobal 等)
_disposed = true;
}
}
// 終結器:防止使用者忘記呼叫 Dispose
~Foo()
{
Dispose(false); // 僅釋放非托管資源
}
public bool AddUser() { /* ... */ return true; }
}
實測 Foo 呼叫 Dispose() 後:
_disposed 變為 true。_foo 非 null,仍可呼叫 AddUser()。Dispose 僅執行邏輯清理,不銷毀物件、不釋放托管記憶體。記憶體回收仍由 GC 決定。對比 Stream(包裝非托管檔案控制代碼):Dispose 後 Length 變 0,因底層控制代碼已關閉,但 Stream 物件本身仍在記憶體中。
| 類型 | 儲存位置 | 生命週期 | 釋放機制 |
|---|---|---|---|
| 實質型別 | Stack(執行緒堆疊) | 隨作用域結束自動彈出 | 編譯器決定性釋放 |
| 參考型別 | Managed Heap(GC 堆) | 直到 GC 判定不可觸達 | GC 非決定性回收 |
classA a = new classA(); 兩步驟:
a。a 指向其位址。離開作用域:Stack 上的 a 移除,Heap 物件成孤兒,等待 GC。
IDisposable。Dispose(bool) 模式,GC.SuppressFinalize 避免雙重釋放。using 區塊或 try/finally 中呼叫 Dispose()。IDisposable(無非托管資源、無堆上分配)。Dispose 不是解構子,不釋放記憶體,僅釋放資源句柄。IDisposable 是「決定性資源釋放」協定,而非記憶體管理工具。理解托管/非托管資源邊界、GC 與終結器互動、Stack/Heap 分配差異,才能正確設計資源管理策略,避免洩漏與效能陷阱。