C# 2.0 引入泛型,徹底改變了 .NET 類型系統。不同於 C++ 模板的文本替換,或 Java 的類型擦除,C# 泛型在運行時保留完整類型資訊,由 CLR 原生支援,實現了真正的參數化多態。本系列基於 Microsoft 官方規範文檔,系統梳理泛型類別、方法、介面、委托、約束、轉換等核心規則。本文為第一篇,聚焦泛型類別聲明與基礎結構。
class_declaration:
attributes_opt class_modifiers_opt class identifier type_parameter_list_opt
class_base_opt type_parameter_constraints_clauses_opt class_body ;_opt
type_parameter_list:尖括號內的類型參數(如 <T>、<K,V>),存在即為泛型類別。泛型類別聲明本身非類型,需提供類型實參構造實際類型:
List<T>、List<List<T>>。僅存在於編譯期。List<int>、List<string>。運行時實際存在,擁有獨立靜態欄位、方法表。命名唯一性:同一作用域內,泛型類別不可與非泛型同名,也不可僅靠參數數量區分(
C、C<T>、C<T,U>視為重複聲明)。但非限定名稱查找、成員訪問規則會考慮參數數量。
type_parameter_list: '<' type_parameters '>'
type_parameters: type_parameter (',' type_parameter)*
type_parameter: attributes_opt identifier
| 限制 | 說明 |
|---|---|
| 不可直接作為基類/介面 | class C<T> : T {} 錯誤。但可用於構造基類:class C<T> : Base<T> {} |
| 成員查找依賴約束 | 無約束時視為 object;有約束則看約束類型成員。 |
| 轉換依賴約束 | 隱式/顯式轉換規則由約束決定(詳見 20.7.4)。 |
null 不可轉換給類型參數 | 除非有 class 約束。替代方案:default(T)。 |
new T() 需建構函式約束 | 需 where T : new()。 |
| 不可用於屬性 | [Attr(typeof(T))] 錯誤。 |
| 不可用於成員訪問/靜態成員/巢狀類型名稱 | T.StaticMethod()、T.Nested 皆錯誤。 |
| Unsafe 代碼中不可作非受控類型 | 不可用於指標、sizeof 等。 |
運行時語義:類型參數純編譯期構件。運行時每個參數綁定至具體運行時類型(封閉類型),所有語句表達式針對實際類型執行。
每個類別聲明關聯一個實例類型:
D)。C<T>)。Outer<T>.Inner<U>)。實例類型是類別內部 this 的類型,僅在類型參數作用域內有效。
Base<string, int[]>)。class Ext<V> : V {} 錯誤)。System.Attribute。所有成員可直接使用類型參數。運行時按構造類型替換實際類型實參:
class C<V> {
public V f1;
public C<V> f2 = null;
public C(V x) { this.f1 = x; this.f2 = this; }
}
// C<int> x1 = new C<int>(1); // f1=int, f2=C<int>
// C<double> x2 = new C<double>(3.14); // f1=double, f2=C<double>
實例函數成員中 this 類型即實例類型。
靜態欄位在相同封閉構造類型的所有實例間共享,但不同封閉構造類型(如 List<int> vs List<string>)不共享。這是因為運行時它們是完全不同的類型。
class C<V> { static int count = 0; public C() { count++; } public static int Count { get { return count; } } }
// C<int>.Count 獨立於 C<double>.Count
靜態建構函式用於初始化靜態欄位,對每個封閉構造類型執行一次。類型參數在作用域內可用於靜態建構函式體,常用於運行時約束檢查(編譯期無法驗證的約束):
class Gen<T> {
static Gen() {
if (default(T) != null) // 若 T 為值類型,default(T) 非 null
throw new ArgumentException("T must be a reference type");
}
}
在泛型類 G 中,透過從 G 構造的類類型實例(或其衍生類實例)訪問繼承的 protected/protected internal 實例成員合法:
class C<T> { protected T x; }
class D<T> : C<T> {
static void F() {
D<T> dt = new D<T>(); dt.x = default(T); // OK
D<int> di = new D<int>(); di.x = 123; // OK
D<string> ds = new D<string>(); ds.x = "test"; // OK
}
}
多載規則:同名函數成員的參數類型在所有可能的封閉構造類型中不得產生相同簽名。約束不參與衝突判斷。
class G1<U> {
long F1(U u); int F1(int i); // 無效:G1<int> 產生雙 F1(int)
void F2(U u1, U u2); void F2(int i, string s); // 有效:U 不可能同時為 int 和 string
void F3(I1<U> a); void F3(I2<U> a); // 有效
void F4(U a); void F4(U[] a); // 有效
}
class G2<U,V> {
void F5(U u, V v); void F5(V v, U u); // 無效:G2<int,int> 衝突
void F6(U u, I1<V> v); void F6(I1<V> v, U u); // 無效:G2<I1<int>,int> 衝突
void F7(U u1, I1<V> v2); void F7(V v1, U u2); // 有效:U 不可能同時為 V 和 I1<V>
void F8(ref U u); void F8(out V v); // 無效:ref/out 不區分簽名
}
class G3<U,V> where U:C1 where V:C2 {
void F9(U u); void F9(V v); // 無效:檢查重載時忽略約束
}
類型參數可用於 params 陣列:
class C<V> { static void F(int x, int y, params V[] args) {} }
// C<int>.F(10,20); → new int[]{}
// C<object>.F(10,20,30,40); → new object[]{30,40}
// C<string>.F(10,20,"hello","goodbye"); → new string[]{"hello","goodbye"}
abstract class C<T> {
public virtual T F() {}
public virtual C<T> G() {}
public virtual void H(C<T> x) {}
}
class D : C<string> {
public override string F() {} // OK
public override C<string> G() {} // OK
public override void H(C<T> x) {} // 錯誤:應為 C<string>
}
class E<T,U> : C<U> {
public override U F() {} // OK
public override C<U> G() {} // OK
public override void H(C<T> x) {} // 錯誤:應為 C<U>
}
++/-- 必須返回實例類型。class X<T> {
public static X<T> operator ++(X<T> op) { ... }
public static int operator *(X<T> op1, int op2) { ... }
public static explicit operator X<T>(T value) { ... }
}
轉換運算子特殊規則:檢查預定義轉換衝突時,類型參數視為「與其他類型無繼承關係的唯一類型」,約束被忽略。Nullable<T> 的轉換運算子設計即利用此規則避開 object 的裝箱轉換衝突(除 T=object 外)。
Outer<T>.Inner<U>。Inner<string> 等價 Outer<T>.Inner<string>(隱式使用外部實例類型)。不可位於泛型類別聲明中。
語法與類別類似,規則大體一致。介面額外有實現唯一性規則(20.3.1):所有可能構造類型的介面列表(含基介面聯集、去重)必須唯一,約束不參與檢查。顯式介面成員實現支援構造介面類型(20.3.2)。
泛型類別聲明是 C# 泛型的基石。理解「開放/封閉構造類型」、「實例類型」、「類型參數限制」、「靜態成員隔離」、「多載衝突規則」、「重寫替換邏輯」等核心概念,是掌握進階泛型編程的前提。下一篇將深入探討構造類型詳細機制、轉換規則、Nullable<T>、別名指令、屬性限制等關鍵內容。