無約束的類型參數僅擁有 object 成員,嚴重限制泛型演算法表達力。約束機制允許在編譯期要求類型實參滿足特定介面、繼承關係或建構函式存在性,從而安全解鎖豐富成員訪問、運算子使用、實例化等能力。本文完整覆蓋約束語法、遵循檢查、成員查找規則、裝箱行為、以及含類型參數的完整轉換矩陣。
type_parameter_constraints_clauses: where_clause+
where_clause: where type_parameter : constraints
constraints:
class_constraint? interface_constraints? constructor_constraint?
class_constraint: class_type (非 sealed、非 Array/Delegate/Enum/ValueType、非 object)
interface_constraints: interface_type (',' interface_type)*
constructor_constraint: new()
where 子句,順序任意。where 為上下文關鍵字,非保留字。where U:T 錯誤)。interface IPrintable { void Print(); }
interface IComparable<T> { int CompareTo(T value); }
interface IKeyProvider<T> { T GetKey(); }
class Printer<T> where T : IPrintable { ... }
class SortedList<T> where T : IComparable<T> { ... }
class Dictionary<K,V>
where K : IComparable<K>
where V : IPrintable, IKeyProvider<K>, new() { ... }
// 錯誤:直接用類型參數作約束
class Extend<T,U> where U : T { ... }
使用構造類型/泛型方法時,對每個 where 子句檢查實參 A:
A 需隱式轉換至約束 C(替換約束中的類型參數後)。允許轉換:同一、隱式引用、裝箱、類型參數隱式轉換。A 非 abstract,且有公共無參建構函式。滿足條件之一即可:值類型(自動有默認建構函式)、非 abstract 類含公共無參建構函式、非 abstract 類含默認建構函式。class B<T> where T : IEnumerable { ... }
class D<T> : B<T> where T : IEnumerable { ... } // 必須重複
class E<T> : B<List<T>> { ... } // List<T> 實現 IEnumerable,無需約束
new() → 同 object 成員集。object 外)隱藏介面成員。object 類中聲明的可用成員,所有介面成員直接從候選集移除。通俗說:類約束成員優先於介面約束成員。
結構重寫 Equals/GetHashCode/ToString,透過類型參數調用不發生裝箱:
struct Counter { int value; public override string ToString() { return (value++).ToString(); } }
static void Test<T>() where T : new() {
T x = new T(); Console.WriteLine(x.ToString()); // 1
Console.WriteLine(x.ToString()); // 2
Console.WriteLine(x.ToString()); // 3
}
Test<Counter>(); // 輸出 1,2,3 無裝箱
介面方法透過類型參數調用,直接作用於實例(非裝箱副本)。但顯式轉介面類型調用會裝箱:
interface ICounter { void Increment(); }
struct Counter : ICounter {
int value;
void ICounter.Increment() { value++; }
}
static void Test<T>() where T : new(), ICounter {
T x = new T(); Console.WriteLine(x); // 0
x.Increment(); // 修改 x
Console.WriteLine(x); // 1
((ICounter)x).Increment(); // 修改 x 的裝箱副本
Console.WriteLine(x); // 1 (未變)
}
| 轉換 | 類型 | 運行時行為 |
|---|---|---|
T → T | 隱式同一 | — |
T → object | 隱式 | 值類型裝箱,引用類型隱式引用 |
object → T | 顯式 | 值類型拆箱,引用類型顯式引用 |
T → interface | 顯式 | 值類型裝箱,引用類型顯式引用 |
interface → T | 隱式 | 值類型拆箱,引用類型顯式引用 |
null → T | 隱式引用 | — |
I 額外轉換T → I (implicit), T → I 的基介面 (implicit)。值類型裝箱,引用類型隱式引用。C 額外轉換T → C (implicit reference), T → C 的衍生類 (implicit), T → C 實現的介面 (implicit)。C → T (explicit reference), C 的衍生類 → T (explicit), C 實現的介面 → T (explicit)。C → A implicit → T → A implicit;A → C explicit → A → T explicit。T[] → U[] 隱式引用轉換,若:同秩,且 U 為 C/其衍生類/實現介面/約束介面/其基介面。反向顯式轉換同條件存在。
防止語義混淆。例:class X<T> { long F(T t) { return (long)t; } }。若允許 T → long 隱式,誤以為 X<int>.F(7) 返回 7L。實際數值轉換僅在編譯時類型已知時考慮。正確寫法:return (long)(object)t; 明確經 object 中轉。
掌握 20.7 章規範,是編寫健壯泛型庫、解讀編譯器錯誤訊息、設計高階泛型 API 的核心功力。全系列五篇至此完結,涵蓋了 C# 2.0 泛型規範的全部核心內容。