C# 語法與 C++ 高度相似,誤以為「只是換個語法」的 C++ 開發者常在記憶體管理、資源釋執行期行為上踩坑。本系列首篇聚焦物件銷毀機制:C++ 確定性解構子 vs C# 非確定性 Finalize/GC,以及 IDisposable 模式的正確實作。
C++ 解構子在物件離開作用域或 delete 時立即、確定性執行。C# 沒有解構子,只有 Finalizer(寫法像解構子 ~Class() 但語意完全不同)。
class Resource {
public:
~Resource() { CloseHandle(h); } // 確定在離開作用域時執行
};
void Foo() {
Resource r; // 區域變數
// ...
} // r.~Resource() 這裡確定執行
class Resource {
~Resource() { // 這是 Finalizer,非解構子!
// GC 執行時才會呼叫,時間不可預測
// 可能程式結束前永不執行
}
}
關鍵差異:
| C++ 解構子 | C# Finalizer (~Class) |
|---|---|
| 確定性執行(離開作用域/delete) | 非確定性(GC 掃描時) |
| 可安全存取其他 managed 物件 | 不可存取其他 managed 物件(可能已被回收) |
| 用於釋放非受控資源 | 僅作為安全網,釋放非受控資源 |
| 可手動呼叫 | 不可手動呼叫(除非基類 Finalizer) |
非受控資源(檔案控制代碼、資料庫連線、GDI 物件、COM 介面)必須顯式釋放,不能等 GC。
public class ResourceHolder : IDisposable
{
private IntPtr handle; // 非受控資源
private bool disposed = false;
// 公開 Dispose:使用者主動呼叫
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 告訴 GC 不需再呼叫 Finalizer
}
// 受保護虛擬核心:true=主動釋放, false=Finalizer 呼叫
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// 釋放受控資源(其他 IDisposable 物件)
// managedResource?.Dispose();
}
// 釋放非受控資源
if (handle != IntPtr.Zero)
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
disposed = true;
}
}
// Finalizer:安全網,僅釋放非受控資源
~ResourceHolder()
{
Dispose(false);
}
}
using (var res = new ResourceHolder())
{
// 使用資源
} // 區塊結束自動呼叫 res.Dispose(),即使拋出例外
某些類別(FileStream、SqlConnection、Graphics)同時提供 Close() 與 Dispose(),兩者功能相同。慣例:
Close()(語意更清晰)。IDisposable.Dispose()。Close() 呼叫 Dispose(true)。
// 錯誤示範
~BadExample()
{
managedObj.DoSomething(); // 危險!managedObj 可能已被 GC 回收
managedObj = null; // 無意義,Finalizer 執行時物件已在回收佇列
}
鐵律:Finalizer (Dispose(false)) 只能釋放非受控資源(IntPtr、HandleRef、P/Invoke 取得的控制代碼),絕不可觸碰任何 managed 參考型別欄位。
若類別可被繼承,Dispose(bool) 必須宣告為 protected virtual,衍生類覆寫時呼叫 base.Dispose(disposing)。若類別 不需繼承,建議加 sealed 避免誤用。
| C++ 概念 | C# 對應 | 關鍵差異 |
|---|---|---|
| 解構子 ~T() | Finalizer ~T() | 執行時機:確定性 vs 非確定性 |
| RAII / 智慧指標 | using + IDisposable | 語法輔助:using 區塊 vs 作用域結束 |
| delete ptr | 無對應 | 記憶體由 GC 管理,不可手動釋放 |
| 釋放非受控資源 | Dispose() + Finalizer 安全網 | 雙重保障模式 |
| 複製建構子/賦值運算子 | 無預設複製語意 | 參考型別預設淺複製,需實作 ICloneable |
IDisposable + Finalizer 雙重模式。IDisposable) → 實作 IDisposable,**不需** Finalizer。using 區塊中使用 IDisposable 物件。Dispose() 內呼叫 GC.SuppressFinalize(this) 避免 GC 額外開銷。Close() 時內部委派給 Dispose(),保持一致性。理解「非確定性銷毀」與「雙重釋放模式」是 C++ 開發者駕馭 C# 資源管理的核心鑰匙,下一篇將探討「值型別 vs 參考型別」、「封箱/拆箱」與「泛型約束」等型別系統陷阱。