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

C# 2.0 泛型規範深度解析(五):約束系統完整篇——類別、介面、建構函式約束與轉換矩陣

     編輯:關於C語言

20.7 約束:為類型參數賦予契約

無約束的類型參數僅擁有 object 成員,嚴重限制泛型演算法表達力。約束機制允許在編譯期要求類型實參滿足特定介面、繼承關係或建構函式存在性,從而安全解鎖豐富成員訪問、運算子使用、實例化等能力。本文完整覆蓋約束語法、遵循檢查、成員查找規則、裝箱行為、以及含類型參數的完整轉換矩陣。

20.7 約束語法

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 { ... }

20.7.1 遵循約束檢查

使用構造類型/泛型方法時,對每個 where 子句檢查實參 A:

  • 類/介面約束:A 需隱式轉換至約束 C(替換約束中的類型參數後)。允許轉換:同一、隱式引用、裝箱、類型參數隱式轉換。
  • new() 約束: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,無需約束

20.7.2 類型參數上的成員查找

  • 無約束或僅 new() → 同 object 成員集。
  • 有類/介面約束 → 合併所有約束類型成員,再移除隱藏成員。
  • 關鍵規則:
    1. 類成員(除 object 外)隱藏介面成員。
    2. 重載決策時,若存在非 object 類中聲明的可用成員,所有介面成員直接從候選集移除。

通俗說:類約束成員優先於介面約束成員。

20.7.3 類型參數與裝箱

結構重寫 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 (未變)
}

20.7.4 含類型參數的轉換完整矩陣

基礎轉換(所有類型參數擁有)

轉換類型運行時行為
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 中轉。

總結:約束系統的設計哲學

  1. 編譯期契約:約束將動態類型需求前移至編譯期,消除運行時轉換失敗。
  2. 最小驚訝原則:轉換規則嚴格區分隱式/顯式、裝箱/拆箱、引用/值,避免隱式數值轉換竄入泛型邏輯。
  3. 裝箱透明化:結構虛擬方法調用、約束介面調用均避免隱式裝箱,保護值類型語義與性能。
  4. 成員查找偏好類:類約束成員優先,符合「類定義行為、介面定義契約」的直覺。
  5. 不繼承約束:強制衍生類顯式聲明需求,避免隱式依賴導致脆弱基類問題。

掌握 20.7 章規範,是編寫健壯泛型庫、解讀編譯器錯誤訊息、設計高階泛型 API 的核心功力。全系列五篇至此完結,涵蓋了 C# 2.0 泛型規範的全部核心內容。

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