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

C# 4.0 協變與逆變深度解析:從委派到介面的型別系統思維

     編輯:關於C語言

前言

C# 4.0 引入泛型協變與逆變,是型別系統邁向更靈活多態的重要一步。但許多開發者僅知 out/in 關鍵字,卻不清其背後的「契約」與「輸入/輸出」本質。本文將從委派、介面兩大應用場景切入,層層剖析協變/逆變的核心邏輯與設計哲學。

核心定義:強弱型別視角

設 Bar : Foo,則 Foo 為弱型別(基類),Bar 為強型別(衍生類)。

  • 協變:將 強型別構建的型別 指派給 弱型別構建的變數(out T)。
  • 逆變:將 弱型別構建的型別 指派給 強型別構建的變數(in T)。

一、委派中的應用

1. 協變委派:回傳值位置

delegate TResult Func(); // 唯讀位置

Func fBar = () => new Bar();
Func fFoo = fBar; // OK: Bar → Foo
Foo foo = fFoo();      // 呼叫端只「讀取」Foo,實際回傳 Bar 符合 Liskov 原則

2. 逆變委派:參數位置

delegate void Action(T arg); // 唯寫位置

Action aFoo = (foo) => Console.WriteLine(foo);
Action aBar = aFoo; // OK: Foo → Bar
aBar(new Bar());         // 呼叫端只「傳入」Bar,實際處理 Foo 亦安全

二、介面中的應用

1. 協變介面:唯讀集合

interface IReadOnlyList : IEnumerable { T this[int i] { get; } }

IReadOnlyList bars = new List();
IReadOnlyList foos = bars; // OK,僅提供「取出」操作

2. 逆變介面:消費者/比較器

interface IComparer { int Compare(T x, T y); }

IComparer cmpFoo = new FooComparer();
IComparer cmpBar = cmpFoo; // OK,僅提供「輸入」操作

三、本質剖析:Func<in T, out TResult> 的啟示

Func<in T, out TResult> 完美詮釋:

  • 參數 (in) = 輸入 = 逆變 = 呼叫端決定傳什麼,實作端必須能處理「更寬鬆」的型別(基類)。
  • 回傳 (out) = 輸出 = 協變 = 實作端決定回什麼,呼叫端只期望「更嚴格」的型別(衍生類)。

因此 輸入參數不可標註 out,回傳值不可標註 in,否則違反型別安全。out/ref 參數兼具輸入輸出,故不可標註任何變異註記。

四、契約觀點:介面即契約,實作者即履約者

  • 介面定義「契約」(方法簽名),實作類別「履約」。
  • 協變:實作類別(強型別)提供的功能超過契約要求(弱型別),安全。
  • 逆變:實作類別(弱型別)接受的輸入涵蓋契約要求(強型別),安全。

五、逆變實現「演算法重用」

逆變讓我們為基類定義一次演算法(如排序、比較、繪圖),自動適用於所有衍生類別,體現「多態」精髓:

interface IPaintable { Color Color { get; set; } }
interface IBrush where T : IPaintable { void Paint(T obj, Color c); }

class Brush : IBrush where T : IPaintable { ... }

// 一把「能塗 IPaintable」的刷子,自動能塗 Car、House 等所有衍生類
IBrush brush = new Brush();
IBrush carBrush = brush; // 逆變指派
carBrush.Paint(new Car(), Color.Red);

總結

協變 = 「繼承」的延伸(子類實例可當父類用);逆變 = 「多態」的深化(父類邏輯可處理子類輸入)。掌握 in = 輸入 = 逆變、out = 輸出 = 協變,即可在設計泛型介面/委派時遊刃有餘。

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