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

C# IDisposable 模式完整實作:手動釋放非託管資源與 GC 協作機制

     編輯:關於C語言

為何需要手動釋放?

.NET GC 僅管理託管記憶體。以下情況需開發者介入:

  1. 持有非託管資源:檔案控制代碼、資料庫連線、COM 物件、原生記憶體指標、GDI 物件等
  2. 大量短命物件:GIS 几何物件、圖形上下文等,等待 GC 會造成記憶體壓力尖峰

標準 Dispose Pattern 實作

using System;

class ResourceHolder : IDisposable {
    private IntPtr _nativeHandle = IntPtr.Zero; // 非託管資源
    private bool _disposed = false;

    // 1. 公共 Dispose:供呼叫端顯式釋放
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this); // 通知 GC 不需再呼叫 Finalize
    }

    // 2. 核心釋放邏輯:disposing=true 來自 Dispose(),false 來自 Finalizer
    protected virtual void Dispose(bool disposing) {
        if (_disposed) return;

        if (disposing) {
            // 釋放託管資源(其他 IDisposable 物件、事件訂閱等)
            // managedResource?.Dispose();
        }

        // 釋放非託管資源(無論 disposing 為何)
        if (_nativeHandle != IntPtr.Zero) {
            NativeMethods.CloseHandle(_nativeHandle);
            _nativeHandle = IntPtr.Zero;
        }

        _disposed = true;
    }

    // 3. 終結器:GC 回收時的安全網
    ~ResourceHolder() {
        Dispose(false);
    }

    // 4. 公開方法使用資源時檢查
    public void DoWork() {
        if (_disposed) throw new ObjectDisposedException(nameof(ResourceHolder));
        // 使用 _nativeHandle
    }
}

// P/Invoke 宣告
internal static class NativeMethods {
    [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool CloseHandle(IntPtr hObject);
}

關鍵細節解析

元素用途關鍵點
Dispose()公共介面,呼叫端主動釋放呼叫 Dispose(true) + GC.SuppressFinalize
Dispose(bool)核心邏輯,virtual 供衍生類別擴充disposing=true:釋放託管+非託管;false:僅非託管
~Finalizer()安全網,防止呼叫端忘記 Dispose呼叫 Dispose(false),不可存取託管物件(可能已被回收)
GC.SuppressFinalize優化:移出 Finalization Queue已手動釋放的物件無需 GC 執行 Finalizer,減少 GC 壓力
_disposed 旗標防止重複釋放多執行緒環境建議用 Interlocked.Exchange

衍生類別正確擴充


class DerivedHolder : ResourceHolder {
    private readonly Stream _stream; // 託管資源

    public DerivedHolder() => _stream = new MemoryStream();

    protected override void Dispose(bool disposing) {
        if (disposing) {
            _stream?.Dispose(); // 釋放衍生類別的託管資源
        }
        // 必須呼叫基底 Dispose 釋放非託管資源
        base.Dispose(disposing);
    }
}

using 陳述式:語法糖保證釋放


using (var holder = new ResourceHolder()) {
    holder.DoWork();
} // 編譯器產生 try/finally 確保 Dispose() 被呼叫

常見錯誤與反模式

  1. Finalizer 中存取託管物件:託管物件可能已被 GC 回收,導致 NullReferenceException 或存取已釋放資源
  2. 未實作 IDisposable 卻有 Finalizer:無意義增加 GC 負擔
  3. Dispose 中拋出例外:導致 GC.SuppressFinalize 不執行,Finalizer 仍會跑
  4. 封裝 SafeHandle 衍生類別:.NET 2.0+ 建議用 SafeHandle 替代手動 IntPtr + Finalizer,自動處理參考計數、關鍵區域

SafeHandle 現代替代方案


class MySafeHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid {
    public MySafeHandle() : base(true) { }
    protected override bool ReleaseHandle() => NativeMethods.CloseHandle(handle);
}

// 使用
using (var h = new MySafeHandle()) {
    NativeMethods.DoSomething(h);
} // 自動呼叫 ReleaseHandle,無需 Finalizer

SafeHandle 內建關鍵區域保護、參考計數、可靠性契約,是處理非託管資源的現代標準。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved