.NET 執行階段透過垃圾收集器 (GC) 自動管理托管記憶體。但許多資源屬於非托管資源:資料庫連線、檔案控制代碼、網路通訊埠、COM 物件、GDI 物件等。GC 無法感知這些資源,必須由開發人員顯式釋放。IDisposable 介面提供標準化的釋放協定。
public class Intro : IDisposable
{
// 非托管資源
private OleDbConnection _conn;
private OleDbCommand _cmd;
private string _introContent;
private bool _disposed = false; // 防重複釋放旗標
public Intro()
{
_conn = DBConnection.GetConn();
_conn.Open();
_cmd = new OleDbCommand("SELECT TOP 1 * FROM [Intro]", _conn);
try
{
using (OleDbDataReader dr = _cmd.ExecuteReader())
{
if (dr.Read())
_introContent = dr.GetValue(1).ToString();
}
}
catch
{
_introContent = "";
}
finally
{
// 建構子中發生異常時,確保已獲取資源能被釋放
_cmd?.Dispose();
_conn?.Close();
}
}
// 公開 Dispose,供使用者主動調用
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 告訴 GC 不需呼叫 Finalize,提升效能
}
// 核心釋放邏輯,disposing 區分「使用者主動釋放」與「GC 最終化釋放」
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 釋放托管資源(實作 IDisposable 的物件)
_cmd?.Dispose();
}
// 釋放非托管資源(無論 disposing 為何)
if (_conn != null)
{
if (_conn.State == ConnectionState.Open)
_conn.Close();
_conn.Dispose();
_conn = null;
}
_disposed = true;
}
// 解構子(最終化器),安全網:使用者忘記呼叫 Dispose 時由 GC 執行
~Intro()
{
Dispose(false);
}
}
using 區塊確保釋放using (Intro intro = new Intro())
{
// 使用 intro...
}
// 區塊結束自動呼叫 intro.Dispose(),即使發生異常也會執行
| 要點 | 說明 |
|---|---|
GC.SuppressFinalize(this) | 在 Dispose() 中調用,移除物件出最終化佇列,避免 GC 執行多餘的 Finalize,減輕 GC 壓力。 |
_disposed 旗標 | 防止重複釋放導致例外(如對已關閉的 Connection 再次呼叫 Close)。 |
Dispose(bool disposing) | 核心虛方法,允許衍生類別覆寫擴展釋放邏輯。disposing=true 時釋放托管+非托管;false 僅釋放非托管(因托管資源可能已被 GC 回收)。 |
| 建構子異常處理 | 建構子中若拋出異常,物件未完全建構,Dispose 不會被自動調用。故在 finally 中清理已成功獲取的資源。 |
| 安全控制代碼 | 推薦使用 SafeHandle 衍生類別 (如 SafeFileHandle) 封裝原生控制代碼,自動處理最終化與釋放,更安全。 |
Dispose(),無 Dispose(bool) 重載 → 衍生類別無法正確擴展清理邏輯。GC.SuppressFinalize → 物件進入最終化佇列,延遲記憶體回收,增加 GC 負擔。Finalize 中訪問托管物件 → 托管物件可能已被回收,引發 ObjectDisposedException 或存取違規。Close() 而非 Dispose() → 部分類別 Close 等同 Dispose,但語意上 Dispose 為標準協定。SafeHandle 管理原生資源public class NativeResourceWrapper : IDisposable
{
private SafeFileHandle _handle;
private bool _disposed;
public NativeResourceWrapper(string path)
{
_handle = File.OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
_handle?.Dispose(); // SafeHandle 內部處理最終化,極其可靠
_disposed = true;
}
}
IDisposable + 完整 Dispose 模式 + 解構子。IDisposable,無需解構子。Dispose(bool),呼叫 base.Dispose(disposing)。IDisposable 物件?→ 以 XML 註解明確標註呼叫端擁有權與釋放責任。using / await using (C# 8+) 確保確定性釋放。Microsoft.VisualBasic.Devices.Computer 等高階封裝已內建妥善處理,優先使用框架提供的類別。