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

C# 資源釋放完全指南:IDisposable、using、Dispose Pattern 與 Finalize 關係圖解

     編輯:關於C語言

為什麼需要手動釋放資源?

.NET GC(垃圾回收器)只負責受控記憶體。對於檔案控制代碼、資料庫連線、網路 Socket、GDI 物件等非受控資源,GC 「不知情」也「無法預測回收時機」。若不主動釋放,極易造成資源洩漏、連線耗盡、檔案被鎖定等嚴重問題。

兩種基礎釋放模式

1. try / finally 手動釋放

TestClass test = new TestClass();
try
{
    test.DoSomething();
}
finally
{
    if (test is IDisposable d) d.Dispose();
}

2. using 區塊(語法糖,編譯器自動展開為 try/finally)

using (TestClass test = new TestClass())
{
    test.DoSomething();
} // 區塊結束自動呼叫 Dispose()
⚠️ using 無法捕獲例外,若需處理例外仍需搭配 try/catch。

IDisposable 介面與標準 Dispose Pattern

僅實作 void Dispose() 尚不足夠,標準模式需解決三大問題:

  1. 重複釋放:Dispose 可被多次呼叫
  2. Finalize 衝突:若已手動 Dispose,GC 不應再呼叫 Finalize
  3. 衍生類別支援:基類釋放邏輯需可被子類擴充

標準 Dispose Pattern 範本

public class ResourceHolder : IDisposable
{
    // 非受控資源(如 IntPtr handle)
    private IntPtr _handle;
    // 受控資源(如其他 IDisposable 物件)
    private Component _components;
    // 防重複釋放旗標
    private bool _disposed = false;

    // 公開 Dispose(供呼叫端使用)
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // 告訴 GC:別再呼叫 Finalize 了
    }

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

        if (disposing)
        {
            // 釋放受控資源
            _components?.Dispose();
        }

        // 釋放非受控資源(無論 disposing 為何)
        CloseHandle(_handle);
        _handle = IntPtr.Zero;

        _disposed = true;
    }

    // Finalizer(析構函數):僅在未呼叫 Dispose 時由 GC 執行
    ~ResourceHolder()
    {
        Dispose(false);
    }

    // 範例:防止釋放後仍被使用
    public void DoSomething()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(ResourceHolder));
        // 正常業務邏輯
    }

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

關鍵細節圖解

情境Dispose(true)Dispose(false)
受控資源✅ 釋放❌ 不碰(GC 會處理)
非受控資源✅ 釋放✅ 釋放(最後防線)
呼叫來源使用者程式碼 / usingGC Finalizer 執行緒

衍生類別正確擴充方式

public class DerivedResource : ResourceHolder
{
    private ManagedResource _managed;
    private NativeResource _native;
    private bool _disposed = false;

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

        try
        {
            if (disposing)
            {
                _managed?.Dispose(); // 釋放衍生類自己的受控資源
            }
            _native?.Close();        // 釋放衍生類自己的非受控資源
        }
        finally
        {
            base.Dispose(disposing); // 必須呼叫基類 Dispose
        }

        _disposed = true;
    }
}

最佳實務清單

  • ✅ 實作 IDisposable 的類別務必遵循標準 Dispose Pattern
  • ✅ 非受控資源一定要在 Dispose(false) 中釋放
  • ✅ 公開 Dispose() 呼叫 GC.SuppressFinalize(this)
  • ✅ 所有公用方法開頭檢查 _disposed,已釋放則拋 ObjectDisposedException
  • ✅ 盡可能使用 using 或 try/finally 確保釋放
  • ⚠️ 避免在 Finalizer 中存取其他受控物件(可能已被回收)
  • ⚠️ Dispose 方法必須可重複呼叫且不拋例外

結語

掌握 IDisposable → Dispose Pattern → Finalize 三角關係,是寫出穩健、無資源洩漏 C# 程式的關鍵。記住:受控資源交給 GC,非受控資源自己負責,Dispose 是橋樑,Finalize 是保險。

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