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

C# 標準 Dispose 模式完整實作指南 (含衍生類別、Finalize、GC.SuppressFinalize)

     編輯:關於C語言

為什麼需要標準模式?

  • 確保**非受控資源** (檔案控制代碼、資料庫連線、GDI 物件、COM、記憶體指標) **一定被釋放**。
  • 提供**決定性釋放** (Dispose()) 供使用者主動呼叫,提升效能。
  • 提供**防禦性最終防線** (Finalize) 防止使用者忘記呼叫 Dispose()。
  • 支援**衍生類別**正確鏈式釋放資源。
  • 避免 Finalize 執行緒額外負擔 (GC.SuppressFinalize)。

標準模式三大核心成員

成員存取層級職責
public void Dispose()public使用者呼叫;釋放受控+非受控資源;呼叫 GC.SuppressFinalize(this)
protected virtual void Dispose(bool disposing)protected virtual核心邏輯:disposing=true 釋放受控+非受控;false 僅釋放非受控;供衍生類別覆寫
~ClassName() (Finalize)無 (編譯器產生)呼叫 Dispose(false);僅作為安全網

基底類別完整實作


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

    // 受控資源範例
    private Stream _stream = null;
    private EventHandler _eventHandler = null;

    // 非受控資源範例
    private IntPtr _handle = IntPtr.Zero; // 檔案控制代碼、GDI 物件等

    // 1. 公開 Dispose - IDisposable 介面
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // 關鍵:告知 GC 不需再呼叫 Finalize
    }

    // 2. 核心虛擬方法 - 衍生類別覆寫點
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return; // 防禦性:避免重複釋放

        if (disposing)
        {
            // ---- 釋放受控資源 ----
            _stream?.Dispose();
            _stream = null;

            // 取消事件訂閱 (防止記憶體洩漏)
            if (_eventHandler != null)
            {
                SomeEventSource.Event -= _eventHandler;
                _eventHandler = null;
            }
        }

        // ---- 釋放非受控資源 (無論 disposing) ----
        if (_handle != IntPtr.Zero)
        {
            CloseHandle(_handle); // P/Invoke
            _handle = IntPtr.Zero;
        }

        _disposed = true;
    }

    // 3. Finalize - 防禦性最終防線
    ~ResourceHolder()
    {
        Dispose(false); // 僅清理非受控資源
    }

    // 公開方法範例:檢查 disposed 狀態
    public void DoWork()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(ResourceHolder));
        // 實際工作...
    }

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

衍生類別正確覆寫模式


public class DerivedResource : ResourceHolder
{
    private bool _disposed = false;
    private AnotherManaged _child = new AnotherManaged();
    private IntPtr _ownHandle = IntPtr.Zero;

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

        if (disposing)
        {
            // 1. 釋放自身受控資源
            _child?.Dispose();
            _child = null;
        }

        // 2. 釋放自身非受控資源
        if (_ownHandle != IntPtr.Zero)
        {
            CloseOwnHandle(_ownHandle);
            _ownHandle = IntPtr.Zero;
        }

        // 3. 呼叫基底類別 Dispose (關鍵!)
        base.Dispose(disposing);

        _disposed = true;
    }

    // 衍生類別若引入新非受控資源,同樣需要 Finalize
    // 但因基底已有 Finalize 呼叫 Dispose(false),此處無需再寫析構函式
    // 若衍生類別為 sealed,可移除 virtual、base.Dispose 呼叫
}

使用者端正確用法


// 方式 1: using 區塊 (推薦,編譯器產生 try/finally)
using (var res = new ResourceHolder())
{
    res.DoWork();
} // 自動呼叫 Dispose()

// 方式 2: 手動 try/finally
var res = new ResourceHolder();
try { res.DoWork(); }
finally { if (res != null) res.Dispose(); }

關鍵規則速查表

  1. 實作 IDisposable 的類別**必須**實作完整模式 (含 Finalize),若持有非受控資源。
  2. 僅持有受控 IDisposable 成員時,**可省略 Finalize**,但仍建議實作完整模式以利衍生。
  3. Dispose(bool) 中:disposing=true → 受控+非受控;false → 僅非受控。
  4. 衍生類別覆寫 Dispose(bool) **必須**呼叫 base.Dispose(disposing)。
  5. GC.SuppressFinalize(this) 僅在 Dispose() (即 disposing=true 路徑) 呼叫。
  6. 所有公開方法開頭檢查 _disposed 拋 ObjectDisposedException。
  7. Finalize 中**絕不**存取其他受控物件 (可能已終結)。
  8. 密封類別 (sealed) 可簡化:移除 virtual、protected,直接實作 Dispose() 邏輯。

常見錯誤與後果

錯誤後果
忘記 GC.SuppressFinalize物件進入 Finalizer Queue,延遲記憶體回收,增加 GC 壓力
Finalize 中呼叫受控物件方法非決定性錯誤、NullReferenceException、資料損毀
衍生類別未呼叫 base.Dispose基底非受控資源洩漏
重複 Dispose 未防禦二次釋放導致例外或資料損毀
公開方法未檢查 _disposed物件被釋放後仍可呼叫方法,行為未定義
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved