泛型約束(where T : ...)讓泛型類別在編譯期強制型別參數滿足特定條件,從而安全呼叫介面方法、存取基底類別成員、或保證建構函數存在。本文以 DisposableList<T> 為例,完整展示 where T : IDisposable 如何實現「移除元素時自動 Dispose()」模式,並詳細解析所有約束類型。
public class DisposableList<T> : IList<T> where T : IDisposable
{
private List<T> _items = new List<T>();
// 核心:移除時自動 Dispose
private void Delete(T item) => item.Dispose();
public void Add(T item) => _items.Add(item);
public void Insert(int index, T item) => _items.Insert(index, item);
public bool Remove(T item)
{
if (_items.Remove(item))
{
Delete(item);
return true;
}
return false;
}
public void RemoveAt(int index)
{
T item = _items[index];
_items.RemoveAt(index);
Delete(item);
}
public void Clear()
{
foreach (var item in _items) Delete(item);
_items.Clear();
}
// 其他 IList<T> 成員委派 _items 實現...
}
var dl = new DisposableList<StreamReader>();
dl.Add(new StreamReader("a.txt"));
dl.Add(new StreamReader("b.txt"));
dl.RemoveAt(0); // 自動呼叫第一個 StreamReader.Dispose()
dl.Remove(reader); // 自動呼叫指定 reader.Dispose()
dl.Clear(); // 自動呼叫所有剩餘元素 Dispose()
// 合法:StreamReader 實作 IDisposable
var ok = new DisposableList<StreamReader>();
// 編譯錯誤:string 未實作 IDisposable
// var error = new DisposableList<string>(); // CS0311
| 約束語法 | 名稱 | 要求 | 典型用途 |
|---|---|---|---|
where T : IDisposable | 介面約束 | T 實作指定介面 | 呼叫介面方法(如 Dispose()、CompareTo()) |
where T : StreamReader | 基底類別約束 | T 繼承自指定類別 | 存取基底類別 protected 成員、呼叫虛擬方法 |
where T : class | 參考型別約束 | T 為 class/interface/delegate/array | 使用 null 比較、as 轉型、鎖定 lock(t) |
where T : struct | 實值型別約束 | T 為非 Nullable 實值型別 | 避免裝箱、使用 default(T)、堆疊配置 |
where T : new() | 無參建構函數約束 | T 有 public 無參建構函數 | 工廠模式 new T()、Dapper 等 ORM 實體建立 |
where T : unmanaged | 非受控型別約束 | T 為 primitive/pointer/enum/struct 僅含 unmanaged 欄位 | 跨語言互操作、stackalloc、Unsafe 操作 |
where T : notnull | 非 Null 約束 | T 為不可為 null 的型別(參考型別啟用 nullable context 時) | 避免 null 檢查、API 設計意圖明確 |
// 必須同時滿足:繼承 TextReader、實作 IDisposable、有無參建構函數
public class AdvancedList<T> where T : TextReader, IDisposable, new()
{
public T Create() => new T(); // 合法
}
約束以逗號分隔,型別參數需同時滿足所有條件。
| 場景 | 無約束 | 有約束 |
|---|---|---|
| 呼叫 T.Dispose() | 編譯錯誤(T 無此方法) | 編譯通過,執行期安全 |
| new T() | 編譯錯誤 | 編譯通過(需 new() 約束) |
| 傳入錯誤型別 | 執行期拋出例外或行為未定義 | 編譯期直接報錯 |
where T : IDisposable 比 where T : StreamReader 更鬆耦合、可測試。new() 就別加,保持泛型通用性。where 子句是 C# 泛型型別系統的基石,將執行期型別檢查前移至編譯期,同時賦予泛型程式碼呼叫特定成員的能力。DisposableList 模式展示了「約束 + 介面」實現資源管理自動化的典範用法。掌握七大約束類型與組合策略,是編寫高品質泛型程式庫的必備技能。