筆記目錄

Skip to content

淺談處置模式模式和 using 陳述式

TLDR

  • 非託管資源(如資料庫連線、檔案)必須手動釋放,垃圾回收器無法自動處理。
  • 實作 IDisposable 介面並在 Dispose() 方法中釋放資源是標準做法。
  • 實作 IDisposable 時應優先使用 Dispose,並透過 GC.SuppressFinalize(this) 阻止 GC 重複呼叫解構式。
  • 若物件同時實作 IDisposableIAsyncDisposable,建議兩者皆實作以確保相容性,且在 ASP.NET Core 中 DisposeAsync() 具有優先權。
  • using 陳述式本質上是 try...finally 的語法糖,能確保資源在離開作用域時被正確釋放。
  • C# 8.0 引入了更簡潔的 using 宣告語法,無需額外的巢狀大括弧即可在作用域結束時自動釋放。

處置模式 (Dispose Pattern)

非託管資源的處理

什麼情況下會遇到這個問題:當程式需要操作資料庫連線、檔案存取或作業系統原生控制代碼 (Handle) 等非託管資源時。

由於 .NET 的垃圾回收器 (GC) 僅管理託管記憶體,無法自動回收非託管資源,開發者必須透過以下方式手動釋放:

  • 實作 IDisposable 介面:在 Dispose() 方法中釋放資源。
  • 宣告解構式 (Finalizer):作為最後一道防線,在 GC 回收物件時自動呼叫。

實作範例

以下為標準的處置模式實作方式,包含防止重複釋放的機制:

csharp
public class ResourceHandle : IDisposable {
    private bool disposed = false;
    private IntPtr unmanagedResource;
    private ManagedResource managedResource;

    public ResourceHandle() {
        managedResource = new ManagedResource();
    }

    public void Dispose() {
        Dispose(true);
        // 告知 GC 此物件已手動釋放,無需再呼叫解構式
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing) {
        if (!disposed) {
            if (disposing) {
                managedResource?.Dispose();
                managedResource = null;
            }

            // 釋放非託管資源
            if (unmanagedResource != IntPtr.Zero) {
                FreeUnmanagedResource(unmanagedResource);
                unmanagedResource = IntPtr.Zero;
            }
            disposed = true;
        }
    }

    ~ResourceHandle() {
        Dispose(false);
    }
}

非同步處置模式

什麼情況下會遇到這個問題:當資源的釋放過程涉及非同步 I/O 操作(如非同步寫入日誌或關閉網路連線)時。

針對 IAsyncDisposable 的實作建議:

  • 同時實作 IDisposableIAsyncDisposable:確保在不支援非同步釋放的舊有程式碼中,資源仍能被正確釋放。
  • DisposeAsync() 優先:在 ASP.NET Core 的依賴注入容器中,若物件同時實作兩者,會優先呼叫 DisposeAsync()
  • 避免死結:在 Dispose(bool disposing) 中不應呼叫非同步方法,僅處理同步釋放邏輯。

using 陳述式

確保資源釋放

什麼情況下會遇到這個問題:當需要確保物件在發生例外 (Exception) 時也能正確釋放資源,避免記憶體洩漏或連線佔用。

using 陳述式會被編譯器轉換為 try...finally 結構,確保 Dispose() 方法一定會被執行。

csharp
// 傳統 using 寫法
using (ResourceHandle handle = new ResourceHandle()) {
    // 執行業務邏輯
}

// C# 8.0 簡潔寫法
{
    using ResourceHandle handle = new ResourceHandle();
    // 離開作用域時自動呼叫 Dispose()
}

巢狀與非同步處理

對於多個資源的釋放,可簡化巢狀結構:

csharp
// 合併宣告
using (ResourceHandle h1 = new ResourceHandle(), h2 = new ResourceHandle()) {
    // 執行邏輯
}

// 非同步釋放
await using (AsyncDisposableObject resource = new AsyncDisposableObject()) {
    // 執行非同步邏輯
}

異動歷程

  • 2024-08-08 初版文件建立。