using 陳述式是 C# 管理非受控資源的核心語法糖,編譯器會將其轉換為 try/finally 區塊並在 finally 中呼叫 IDisposable.Dispose()。本文從原理、用法、進階模式到常見錯誤全方位解析,助你寫出更安全、高效的資源管理程式碼。
// 寫法
using (var conn = new SqlConnection(connStr))
{
conn.Open();
// 使用連線...
}
// 編譯器實際產生的 IL 邏輯等同於:
SqlConnection conn = null;
try
{
conn = new SqlConnection(connStr);
conn.Open();
// 使用連線...
}
finally
{
if (conn != null)
((IDisposable)conn).Dispose(); // 即使 try 區塊拋出例外也會執行
}
關鍵點:
using 區塊結束時保證呼叫 Dispose()(除非進程被強制終止)。IDisposable 的任何型別:資料庫連線、檔案流、圖形物件、網路串流、非受控記憶體等。Dispose() 應具備冪等性(多次呼叫不拋例外、不重複釋放)。
// 多資源巢狀 using(C# 8.0 前)
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(sql, conn))
{
conn.Open();
cmd.ExecuteNonQuery();
}
// C# 8.0+ 簡化語法(無大括號,範圍至方法結束)
using var conn = new SqlConnection(connStr);
using var cmd = new SqlCommand(sql, conn);
conn.Open();
cmd.ExecuteNonQuery();
// 方法結束時自動按宣告相反順序 Dispose
public class ResourceHolder : IDisposable
{
private bool _disposed = false;
private IntPtr _unmanagedHandle; // 非受控資源
private FileStream _managedStream; // 受控資源
// 建構子取得資源
public ResourceHolder(string path)
{
_managedStream = File.OpenRead(path);
_unmanagedHandle = SomeNativeApi.Open(path);
}
// 公開 Dispose(供 using 呼叫)
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 告訴 GC 不需再呼叫 Finalizer
}
// 核心釋放邏輯
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 釋放受控資源
_managedStream?.Dispose();
}
// 釋放非受控資源
if (_unmanagedHandle != IntPtr.Zero)
{
SomeNativeApi.Close(_unmanagedHandle);
_unmanagedHandle = IntPtr.Zero;
}
_disposed = true;
}
// 解構函式(Finalizer)—— 保底防線
~ResourceHolder()
{
Dispose(false); // 僅釋放非受控資源
}
}
// IAsyncDisposable (C# 8.0 / .NET Core 3.0+)
await using var stream = new FileStream("data.bin", FileMode.Open);
var buffer = new byte[1024];
await stream.ReadAsync(buffer, 0, buffer.Length);
// 範圍結束自動呼叫 await stream.DisposeAsync()
public static SqlConnection CreateOpenConnection(string connStr)
{
var conn = new SqlConnection(connStr);
conn.Open();
return conn; // 呼叫端需 using 接收
}
// 使用
using (var conn = CreateOpenConnection(connStr))
{
// ...
}
| 錯誤寫法 | 問題 | 正確做法 |
|---|---|---|
var conn = new SqlConnection(); using(conn) {} | 若建構子拋例外,conn 為 null,using 仍會嘗試 Dispose()(安全但多餘) | 直接在 using 宣告中實例化:using (var conn = new SqlConnection()) |
在 using 區塊內重新指派變數 | 原物件洩漏,新物件可能未被 Dispose | 勿重新指派;需切換連線時用巢狀 using 或先 Dispose 再建新 |
using (var ms = new MemoryStream()) { return ms.ToArray(); } | 回傳前 Dispose() 關閉串流,ToArray() 仍可運作但語意混淆 | 明確區分:using 管理生命週期,回傳資料而非串流本身 |
忘記實作 IDisposable 就用 using | 編譯錯誤 | 類別需實作 IDisposable(或繼承實作的基類) |
Dispose() 中拋出例外 | 中斷 finally 流程,導致後續資源未釋放 | Dispose() 內部 切勿拋出例外,錯誤應記錄並吞噬 |
catch 中處理特定例外並決定是否釋放、資源跨方法/類別共享、條件式建立資源(如 if (need) using... 邏輯複雜時)。編譯器優化下,using 不產生額外堆疊框架或物件配置。Dispose() 為非虛擬介面呼叫(介面限定),JIT 可內聯,開銷可忽略。大量迴圈中建立/釋放資源時,考慮物件池而非頻繁 using new。
IDisposable 物件 → 立即包在 using 中。GC.SuppressFinalize)。await using + IAsyncDisposable。Dispose() 內部 不拋例外,冪等、可重入。using 區塊外長期持有已 Dispose 的物件參考。掌握 using 的編譯行為與 IDisposable 正確實作,是寫出健壯、無資源洩漏 C# 程式的基石。