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

C# 2.0 泛型規範深度解析(二):泛型方法、委托、構造類型詳解與轉換規則

     編輯:關於C語言

20.1.13~20.5:從泛型類別延伸至構造類型完整體系

接續上篇,本文涵蓋泛型方法、委托聲明、構造類型的完整定義(開放/封閉、基類介面繼承、成員替換、可訪問性、轉換規則)、Nullable<T>、別名指令與屬性限制。

20.6 泛型方法

聲明語法

generic_method_declaration:
  attributes_opt  method_modifiers_opt  return_type  member_name
  type_parameter_list  ( formal_parameter_list_opt )
  type_parameter_constraints_clauses_opt  method_body
  • 類型參數列表緊隨方法名之後(Find<T>(T[] items, Test<T> test))。
  • 作用域:返回類型、參數列表、方法體、約束子句(不含屬性)。
  • 不可聲明為 extern,其他修飾符皆可用。

20.6.1 泛型方法簽名

  • 簽名比較忽略約束、忽略類型參數名稱,但計算參數數量與順序。
  • 同一類別中,兩個同名泛型方法若在某種封閉構造類型下產生相同簽名 → 錯誤(約束不參與檢查)。
interface IX {
  T F1<T>(T[] a, int i);  // 錯誤:返回類型與參數名無關,兩者簽名相同
  void F1<U>(U[] a, int i);
  void F2<T>(int x); void F2(int x); // OK:類型參數數量不同
  void F3<T>(T t) where T:A; void F3<T>(T t) where T:B; // 錯誤:約束不計入簽名
}

20.6.2 虛擬泛型方法

  • 支援 abstract/virtual/override。
  • 重寫/實現時,每個類型參數的約束必須完全一致(按左到右位置對應)。
abstract class Base {
  public abstract T F<T,U>(T t, U u);
  public abstract T G<T>(T t) where T: IComparable;
}
class Derived : Base {
  public override X F<X,Y>(X x, Y y) { } // OK:參數名可不同
  public override T G<T>(T t) { }       // 錯誤:缺少 where T:IComparable
}

20.6.3 呼叫泛型方法:顯式實參 vs 類型推斷

  • 可顯式指定:Find<int>(arr, test)。
  • 省略實參 → 編譯器依類型推斷(20.6.4)推導。
  • 重載決策在類型推斷之後進行。
static void F<T>(int x, T y) { Console.WriteLine("One"); }
static void F<T>(T x, long y) { Console.WriteLine("Two"); }

F<int>(5, 324);   // "One"
F<byte>(5, 324);  // "Two"
F<double>(5, 324); // 錯誤:模糊
F(5, 324);        // "One" (推斷 T=int)
F(5, 324L);       // 錯誤:模糊

20.6.4 類型推斷算法詳解

輸入:方法組、實參列表。輸出:每個類型參數的推斷類型實參。

  1. 實參數量不匹配 → 立即失敗。
  2. 對每個實參 A 對應參數 P 獨立推斷:
  • P 與方法類型參數無關 / 實參為 null / 匿名方法 / 方法組 → 不推斷任何類型(但推斷成功)。
  • P 為陣列類型,A 同秩陣列 → 遞歸用元素類型推斷;秩不同 → 失敗。
  • P 為方法類型參數 MX → 推斷類型為 A。
  • P 為構造類型 → 對 P 中每個類型參數 MX 求唯一 TX 使 A 隱式轉換至 P[MX:=TX]。多解/無解 → 失敗。(約束忽略)
  1. 匯聚所有推斷:必須完整(每參數有推斷)且一致(同參數推斷相同類型)。

params 方法:先試普通形式,失敗再試擴展形式。

20.6.5 語法歧義消解

F(G<A,B>(7)):> 後緊跟 { } ] > : ; , . ? 之一 → 視為類型實參列表結束;否則視為大於運算子。

20.6.6 委托使用泛型方法

  • 委托實例化時確定類型實參(顯式或推斷)。
  • 推斷時僅用委托參數類型,不用返回類型。
delegate int D(string s, int i);
delegate int E();
class X {
  public static T F<T>(string s, T t) { ... }
  public static T G<T>() { ... }
  static void Main() {
    D d1 = new D(F<int>); // 顯式
    D d2 = new D(F);      // 推斷 int
    E e1 = new E(G<int>); // 顯式
    E e2 = new E(G);      // 錯誤:無法從返回類型推斷
  }
}

20.6.7 非泛型屬性/事件/索引子/運算子

它們自身不能有類型參數(雖可在泛型類中使用外部類型參數)。需泛型屬性語義時,請改用泛型方法。

20.7 約束

語法

where  type_parameter  :  class_constraint?  interface_constraints?  constructor_constraint?
class_constraint: class_type (非 sealed、非 System.Array/Delegate/Enum/ValueType、非 object)
interface_constraint: interface_type (不可重複)
constructor_constraint: new()
  • 每參數最多一 where 子句,順序任意。
  • 約束類型必須可訪問。
  • 不可單獨用類型參數作約束(where U:T 錯誤)。

20.7.1 遵循約束檢查

  • 類/介面約束:實參 A 需隱式轉換至約束 C(同一/隱式引用/裝箱/類型參數轉換)。
  • new():A 非 abstract,且有公共無參建構函式(值類型自動滿足)。
  • 約束不繼承:衍生類必須重新聲明滿足基類約束的約束(除非基類實參已滿足,如 List<T> : IEnumerable)。

20.7.2 類型參數上的成員查找

  • 無約束或僅 new() → 同 object 成員集。
  • 有類/介面約束 → 合併所有約束類型成員,再移除隱藏成員。
  • 類成員優先於介面成員:重載決策時,若有類成員候選,介面成員直接移除。

20.7.3 類型參數與裝箱

  • 結構重寫 Equals/GetHashCode/ToString,透過類型參數調用不裝箱。
  • 約束介面方法在結構實例上調用不裝箱(直接修改實例);但強制轉介面類型調用會裝箱修改副本。
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(); x.Increment();      // 修改 x
  ((ICounter)x).Increment();         // 修改 x 的裝箱副本
}

20.7.4 含類型參數的轉換

無約束/任意約束參數 T 擁有:

  • T → T (identity), T → object (implicit), object → T (explicit), T → interface (explicit), interface → T (implicit)。

若約束介面 I:T → I (implicit), T → I 的基介面 (implicit)。
若約束類 C:T → C (implicit reference), C → T (explicit reference), 以及衍生類/實現介面的相互轉換。
用戶定義轉換傳遞規則同樣適用。
null → T 隱式引用轉換始終存在。
陣列協變:T[] → U[] 隱式引用轉換,若 U 為 C/其衍生類/實現介面/約束介面/其基介面。

20.5 構造類型完整規範

20.5.1 類型實參

每個實參為一個類型,可為構造類型或類型參數。Unsafe 代碼中不可為指標類型。必須滿足對應參數約束。

20.5.2 開放 vs 封閉類型(遞歸定義)

  • 類型參數 → 開放。
  • 陣列類型 → 元素開放則開放。
  • 構造類型 → 任一實參開放則開放。
  • 非開放即封閉。

運行時僅存在封閉類型,開放類型純編譯期概念。每個封閉構造類型擁有獨立靜態變量集。相同聲明+相同實參 → 同一類型。

20.5.3 構造類型的基類與介面

遞歸替換類型參數獲得。例:D<T> : C<T[]>, C<U> : B<IComparable<U>>, B<V> : A → D<int> 基類鏈:C<int[]>, B<IComparable<int[]>>, A, object。

20.5.4 構造類型的成員

非繼承成員:聲明中類型參數替換為實參(語義替換,非文本)。繼承成員:先確定直接基類構造類型成員,再替換當前類型參數。

20.5.5 可訪問性

C<T1..Tn> 可訪問 ⟺ C 與所有 Ti 均可訪問。最小可訪問性原則。

20.5.6 轉換:無協變/逆變

構造引用類型不支援協變:List<B> 不能轉 List<A>(即使 B:A)。否則可存入 A 實例破壞 List<B> 不變性。同理無 List<B> → List<object> 轉換。

List<A> listA = new List<A>();
List<B> listB = new List<B>();
Collection c1 = listA; // OK
Collection c2 = listB; // OK
List<A> a1 = listB;    // 錯誤:無隱式
List<A> a2 = (List<A>)listB; // 錯誤:無顯式

20.5.7 System.Nullable<T>

表示可為 null 的值類型。null 隱式轉換至任意 Nullable<T>,結果為默認值(HasValue=false)。等價於 default(Nullable<T>)。

20.5.8 別名指令

using Alias = ClosedConstructedType; 合法;using Alias = GenericTypeDeclaration; 非法。

20.5.9 屬性限制

開放類型不可用於屬性。封閉構造類型可作屬性實參,但不可作屬性類型名(因 System.Attribute 不可為泛型基類)。

本篇小結

本文完整覆蓋了泛型方法簽名與推斷、委托實例化、約束體系(含裝箱行為)、轉換規則矩陣、構造類型的完整生命週期(基類介面繼承、成員替換、可訪問性、協變禁令)以及 Nullable<T>、別名、屬性等邊界規則。這些是編寫高階泛型庫、理解編譯器錯誤訊息、設計 API 簽名的必備知識。下一篇將專門講解匿名方法規範(第 21 章)。

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