C# 的記憶體管理雖由垃圾收集器(GC)自動處理,但涉及非受控資源(檔案控制代碼、資料庫連線、網路 Socket、視窗控制代碼等)時,必須顯式釋放。本文系統闡述解構函式(Finalizer)、Dispose 模式、兩者關係與最佳實踐,附完整代碼範例。
class Car {
~Car() { // 解構函式語法
// 清理非受控資源
}
}
protected override void Finalize(),內含 try/finally 呼叫 base.Finalize(),確保繼承鏈從衍生到基底依序執行。Finalize → 釋放記憶體。GC.Collect() 強制回收,但極不建議常規使用,會嚴重影響效能。| 維度 | 解構函式 (~Class) | Dispose() |
|---|---|---|
| 呼叫者 | GC(非決定性) | 開發人員代碼(決定性) |
| 介面要求 | 無 | 實作 IDisposable |
| 釋放範圍 | 僅非受控資源 | 受控 + 非受控資源 |
| 效能影響 | 延遲回收,額外 Finalizer 執行緒開銷 | 立即釋放,無額外 GC 壓力 |
| 繼承鏈責任 | 自動鏈式呼叫 base.Finalize() | 需手動鏈式呼叫 base.Dispose() |
當 Finalize 執行時,該物件引用的其他受控物件可能已被 GC 回收(順序不定),若在解構函式中操作它們會導致例外或未定義行為。因此解構函式僅清理直接持有的非受控資源。
public class ResourceHolder : IDisposable {
private IntPtr unmanagedHandle; // 非受控資源
private Component managedResource; // 受控資源
private bool disposed = false;
// 公開 Dispose,供呼叫端決定性釋放
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this); // 告訴 GC 不需再呼叫 Finalize
}
// 核心清理邏輯
protected virtual void Dispose(bool disposing) {
if (disposed) return;
if (disposing) {
// 釋放受控資源
managedResource?.Dispose();
}
// 釋放非受控資源(無論 disposing 為何)
if (unmanagedHandle != IntPtr.Zero) {
CloseHandle(unmanagedHandle);
unmanagedHandle = IntPtr.Zero;
}
disposed = true;
}
// 解構函式作為安全網
~ResourceHolder() {
Dispose(false); // 僅釋放非受控資源
}
// P/Invoke 釋放非受控資源
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
}
// 基底類別
public class BaseClass : IDisposable {
protected bool disposed = false;
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (!disposed) {
if (disposing) { /* 釋放受控資源 */ }
/* 釋放非受控資源 */
disposed = true;
}
}
~BaseClass() { Dispose(false); }
}
// 衍生類別
public class DerivedClass : BaseClass {
private Resource derivedResource;
protected override void Dispose(bool disposing) {
if (!disposed) {
if (disposing) {
derivedResource?.Dispose(); // 釋放衍生類別受控資源
}
/* 釋放衍生類別非受控資源 */
disposed = true;
}
base.Dispose(disposing); // 呼叫基底 Dispose
}
// 衍生類別通常不需重寫解構函式,基底已處理
}
class First { ~First() { Trace.WriteLine("First's destructor called."); } }
class Second : First { ~Second() { Trace.WriteLine("Second's destructor called."); } }
class Third : Second { ~Third() { Trace.WriteLine("Third's destructor called."); } }
class Test {
static void Main() {
Third t = new Third();
}
}
// 輸出順序(GC 觸發時):
// Third's destructor called.
// Second's destructor called.
// First's destructor called.
GC.SuppressFinalize(this)。Dispose(false))。using 區塊:確保即使發生例外也能釋放資源。
using (var holder = new ResourceHolder()) {
// 使用資源
} // 自動呼叫 Dispose()
SafeHandle 衍生類別封裝非受控資源(.NET 2.0+ 推薦)。解構函式是 GC 的最後防線,Dispose 是開發者的主動權。掌握標準 Dispose 模式,善用 using,讓非受控資源釋放既安全又高效,是 C# 專業開發者的必修課。