淺談處置模式模式和 using 陳述式
TLDR
- 非託管資源(如資料庫連線、檔案)需透過實作
IDisposable介面或定義完成項(Finalizer)手動釋放。 - 實作處置模式(Dispose Pattern)時,應優先使用
Dispose()方法,並透過GC.SuppressFinalize(this)阻止垃圾回收器重複呼叫完成項。 IAsyncDisposable用於非同步釋放資源,建議同時實作IDisposable以維持向後相容性。using陳述式本質上是try...finally的語法糖,確保物件在離開作用域時能正確呼叫Dispose()。- C# 8.0 引入的
using宣告語法可減少巢狀縮排,提升程式碼可讀性。
處置模式 (Dispose Pattern)
非託管資源的釋放
什麼情況下會遇到這個問題:當開發者使用如資料庫連線、檔案存取等非託管資源(Unmanaged Resources)時,由於 CLR 的垃圾回收器(GC)無法自動管理這些資源,必須手動釋放。
釋放非託管資源主要有兩種方式:
- 實作
IDisposable介面:在Dispose()方法中手動釋放資源。 - 宣告完成項 (Finalizer):在 GC 回收物件時自動呼叫,但應優先使用
Dispose()方法。
實作範例
實作處置模式時,建議使用以下標準模式以確保資源被正確釋放且不會重複執行:
csharp
public class ResourceHandle : IDisposable {
private bool disposed = false;
private IntPtr unmanagedResource;
private ManagedResource managedResource;
public ResourceHandle() {
unmanagedResource = IntPtr.Zero;
managedResource = new ManagedResource();
}
public void Dispose() {
Dispose(true);
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);
}
}非同步處置模式
什麼情況下會遇到這個問題:當資源的釋放過程涉及非同步操作(如網路請求)時,應使用 .NET Core 3.0 引入的 IAsyncDisposable 介面。
- 最佳實作建議:同時實作
IDisposable與IAsyncDisposable,以確保在不支援非同步釋放的舊有框架中,資源仍能被正確處理。 - 優先順序:在 ASP.NET Core 的依賴注入(DI)容器中,若物件同時實作兩者,會優先呼叫
DisposeAsync()。
csharp
class ExampleConjunctiveDisposable : IDisposable, IAsyncDisposable {
IDisposable? disposableResource = new MemoryStream();
IAsyncDisposable? asyncDisposableResource = new MemoryStream();
public void Dispose() {
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync() {
await DisposeCoreAsync().ConfigureAwait(false);
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
disposableResource?.Dispose();
disposableResource = null;
if (asyncDisposableResource is IDisposable disposable) {
disposable.Dispose();
asyncDisposableResource = null;
}
}
}
protected virtual async ValueTask DisposeCoreAsync() {
if (asyncDisposableResource is not null) {
await asyncDisposableResource.DisposeAsync().ConfigureAwait(false);
}
if (disposableResource is IAsyncDisposable disposable) {
await disposable.DisposeAsync().ConfigureAwait(false);
} else {
disposableResource?.Dispose();
}
asyncDisposableResource = null;
disposableResource = null;
}
}using 陳述式
確保釋放資源
什麼情況下會遇到這個問題:為了避免因忘記呼叫 Dispose() 而導致資源洩漏,或在發生異常時無法執行清理邏輯。
編譯器會將 using 陳述式自動轉換為 try...finally 結構,確保無論是否發生異常,資源皆會被釋放。
csharp
// 傳統寫法
using (ResourceHandle handle = new ResourceHandle()) {
// 執行邏輯
}
// C# 8.0 新語法:離開作用域時自動呼叫 Dispose()
{
using ResourceHandle handle = new ResourceHandle();
// 執行邏輯
}巢狀與非同步處理
針對多個資源的釋放,可簡化巢狀結構:
- 合併宣告:若型別相同,可合併在同一個
using中。 - 非同步釋放:若物件實作
IAsyncDisposable,需使用await using。
csharp
// 合併宣告
using (ResourceHandle handle1 = new ResourceHandle(), handle2 = new ResourceHandle()) {
// 執行邏輯
}
// 非同步釋放
await using (AsyncDisposableObject resource = new AsyncDisposableObject()) {
// 執行邏輯
}異動歷程
- 初版文件建立。