首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 標準 Dispose 模式完整實作:非受控資源釋放的最佳實務

     編輯:關於C語言

為何需要 Dispose 模式

.NET 垃圾收集器 (GC) 僅管理記憶體 (受控資源)。檔案控制代碼、資料庫連線、GDI 物件、COM 介面、Unmanaged 記憶體等 **非受控資源** 必須由開發者明確釋放。標準 Dispose 模式提供:

  1. 客戶端主動釋放 (IDisposable.Dispose)。
  2. 最終防線:終結器 (Finalize/~Class) 兜底。
  3. 避免重複釋放、抑制終結器效能開銷 (GC.SuppressFinalize)。
  4. 衍生類安全擴充點 (protected virtual Dispose(bool))。

核心模式程式碼


public class ResourceHolder : IDisposable
{
    // 1. 狀態旗標
    private bool _disposed = false;

    // 2. 非受控資源 (範例: 檔案控制代碼)
    private IntPtr _handle = IntPtr.Zero;
    private SafeFileHandle _safeHandle; // 建議用 SafeHandle 封裝

    // 3. 公用 Dispose - 客戶端呼叫
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // 關鍵:已手動清理,告知 GC 不需呼叫終結器
    }

    // 4. 核心虛擬方法 - 衍生類覆寫點
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return; // 防禦性:多次呼叫安全

        if (disposing)
        {
            // 釋放受控資源 (Managed): 其他 IDisposable 物件、事件取消訂閲等
            _safeHandle?.Dispose();
            _safeHandle = null;
        }

        // 釋放非受控資源 (Unmanaged): 總是執行
        if (_handle != IntPtr.Zero)
        {
            CloseHandle(_handle); // P/Invoke
            _handle = IntPtr.Zero;
        }

        _disposed = true;
    }

    // 5. 終結器 - 最終防線
    ~ResourceHolder()
    {
        Dispose(false); // disposing=false:僅清理非受控資源
    }

    // 6. 公開方法檢查 disposed 狀態
    public void DoSomething()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(ResourceHolder));
        // 正常邏輯...
    }

    // P/Invoke 範例
    [System.Runtime.InteropServices.DllImport("kernel32.dll")]
    private static extern bool CloseHandle(IntPtr hObject);
}

衍生類正確擴充模式


public class DerivedResource : ResourceHolder
{
    private bool _disposed = false;
    private MemoryStream _managedStream;

    protected override void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            // 先釋放自己的受控資源
            _managedStream?.Dispose();
            _managedStream = null;
        }

        // 再釋放自己的非受控資源 (若有)
        // ...

        // 最後呼叫基類 Dispose (基類會設定 _disposed=true)
        base.Dispose(disposing);

        _disposed = true; // 衍生類自己的旗標
    }
}

關鍵規則總表

情境Dispose(true)Dispose(false) / 終結器
受控資源 (Managed)✅ 釋放❌ 不觸碰 (可能已被 GC 回收)
非受控資源 (Unmanaged)✅ 釋放✅ 釋放 (最後機會)
呼叫基類✅ base.Dispose(true)✅ base.Dispose(false)
GC.SuppressFinalize✅ 在公用 Dispose()❌ 從不呼叫

現代最佳實務:SafeHandle 取代手動 P/Invoke


public class ModernResource : IDisposable
{
    private SafeFileHandle _handle; // 衍生自 SafeHandleZeroOrMinusOneIsInvalid
    private bool _disposed;

    public ModernResource(string path)
    {
        _handle = File.OpenHandle(path); // .NET 6+ 回傳 SafeFileHandle
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing) _handle?.Dispose(); // SafeHandle 內建終結器,雙重保障
        _disposed = true;
    }

    public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
    ~ModernResource() { Dispose(false); }
}

SafeHandle 內建終結器、參考計數、Critical Finalizer Object (CFO) 特性,極大降低資源洩漏風險。

常見錯誤與避坑指南

  • ❌ 終結器中存取其他受控物件 (可能已終結) → ✅ 僅清理非受控資源。
  • ❌ 終結器中復活物件 (GlobalList.Add(this)) → ✅ 絕不復活,導致終結器不再執行、資源永不釋放。
  • ❌ Dispose 拋出例外 → ✅ 釋放程式碼不應拋出例外,記錄錯誤即可。
  • ❌ 忘記 GC.SuppressFinalize → ✅ 公用 Dispose() 必須呼叫,避免效能損耗。
  • ❌ 實作 IDisposable 但無終結器 (含非受控資源時) → ✅ 有非受控資源必須有終結器。

using 模式與 C# 8 using 宣告


// 傳統
using (var res = new ResourceHolder()) { res.DoSomething(); }

// C# 8+ using 宣告 (編譯器自動在作用域結束呼叫 Dispose)
using var res = new ResourceHolder();
res.DoSomething();
// 隱含 try/finally 區塊結束時呼叫 Dispose()
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved