首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 2.0 泛型規範深度解析(一):泛型類別聲明、類型參數、實例類型與基類約束

     編輯:關於C語言

前言:泛型——C# 類型系統的里程碑

C# 2.0 引入泛型,徹底改變了 .NET 類型系統。不同於 C++ 模板的文本替換,或 Java 的類型擦除,C# 泛型在運行時保留完整類型資訊,由 CLR 原生支援,實現了真正的參數化多態。本系列基於 Microsoft 官方規範文檔,系統梳理泛型類別、方法、介面、委托、約束、轉換等核心規則。本文為第一篇,聚焦泛型類別聲明與基礎結構。

20.1 泛型類別聲明

語法結構

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>),存在即為泛型類別。
  • 巢狀在泛型類別內的類別,隱含為泛型類別(需提供外部類型參數)。

構造類型:開放 vs 封閉

泛型類別聲明本身非類型,需提供類型實參構造實際類型:

  • 開放構造類型:含類型參數,如 List<T>、List<List<T>>。僅存在於編譯期。
  • 封閉構造類型:無類型參數,如 List<int>、List<string>。運行時實際存在,擁有獨立靜態欄位、方法表。

命名唯一性:同一作用域內,泛型類別不可與非泛型同名,也不可僅靠參數數量區分(C、C<T>、C<T,U> 視為重複聲明)。但非限定名稱查找、成員訪問規則會考慮參數數量。

20.1.1 類型參數

聲明與作用域

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 等。

運行時語義:類型參數純編譯期構件。運行時每個參數綁定至具體運行時類型(封閉類型),所有語句表達式針對實際類型執行。

20.1.2 實例類型

每個類別聲明關聯一個實例類型:

  • 非泛型類:聲明類型本身(D)。
  • 泛型類:用自身類型參數構造的類型(C<T>)。
  • 巢狀類:外部實例類型 + 內部類型參數(Outer<T>.Inner<U>)。

實例類型是類別內部 this 的類型,僅在類型參數作用域內有效。

20.1.3 基類規範

  • 基類可以是構造類型(Base<string, int[]>)。
  • 基類不能是類型參數(class Ext<V> : V {} 錯誤)。
  • 不可直接/間接繼承 System.Attribute。

20.1.4 泛型類別的成員

所有成員可直接使用類型參數。運行時按構造類型替換實際類型實參:

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 類型即實例類型。

20.1.5 靜態欄位:每個封閉構造類型獨享

靜態欄位在相同封閉構造類型的所有實例間共享,但不同封閉構造類型(如 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

20.1.6 靜態建構函式:每個封閉類型執行一次

靜態建構函式用於初始化靜態欄位,對每個封閉構造類型執行一次。類型參數在作用域內可用於靜態建構函式體,常用於運行時約束檢查(編譯期無法驗證的約束):

class Gen<T> {
  static Gen() {
    if (default(T) != null) // 若 T 為值類型,default(T) 非 null
      throw new ArgumentException("T must be a reference type");
  }
}

20.1.7 訪問受保護成員:泛型擴展規則

在泛型類 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
  }
}

20.1.8 泛型類別中的多載:避免構造類型簽名衝突

多載規則:同名函數成員的參數類型在所有可能的封閉構造類型中不得產生相同簽名。約束不參與衝突判斷。

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); // 無效:檢查重載時忽略約束
}

20.1.9 參數陣列與類型參數

類型參數可用於 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"}

20.1.10 重寫與泛型類別

  • 基類為非泛型/封閉構造類型 → 重寫成員不能含類型參數。
  • 基類為開放構造類型 → 重寫成員可使用聲明中的類型參數。
  • 基類成員先按類型實參替換確定,再應用普通重寫規則。
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>
}

20.1.11 泛型類別中的運算子

  • 單元運算子:單一參數為實例類型;++/-- 必須返回實例類型。
  • 二元運算子:至少一參數為實例類型。
  • 轉換運算子:參數與返回類型皆為實例類型。
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 外)。

20.1.12 巢狀類型:隱式泛型與隱式外部類型實參

  • 泛型類別內的巢狀類型隱式為泛型(含外部類型參數)。
  • 外部引用需完整限定:Outer<T>.Inner<U>。
  • 外部類內部可簡寫:Inner<string> 等價 Outer<T>.Inner<string>(隱式使用外部實例類型)。
  • 巢狀類型參數可隱藏外部參數(不推薦)。

20.1.13 應用程式進入點

不可位於泛型類別聲明中。

20.2 泛型結構聲明 & 20.3 泛型介面聲明

語法與類別類似,規則大體一致。介面額外有實現唯一性規則(20.3.1):所有可能構造類型的介面列表(含基介面聯集、去重)必須唯一,約束不參與檢查。顯式介面成員實現支援構造介面類型(20.3.2)。

本篇小結

泛型類別聲明是 C# 泛型的基石。理解「開放/封閉構造類型」、「實例類型」、「類型參數限制」、「靜態成員隔離」、「多載衝突規則」、「重寫替換邏輯」等核心概念,是掌握進階泛型編程的前提。下一篇將深入探討構造類型詳細機制、轉換規則、Nullable<T>、別名指令、屬性限制等關鍵內容。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved