C#泛型的逆變協變(個人理解)

来源:https://www.cnblogs.com/CollapseNav/archive/2023/04/04/17285595.html
-Advertisement-
Play Games

前編 一般來說, 泛型的作用就類似一個占位符, 或者說是一個參數, 可以讓我們把類型像參數一樣進行傳遞, 儘可能地復用代碼 我有個朋友, 在使用的過程中發現一個問題 IFace<object> item = new Face<string>(); // CS0266 public interface ...


前編

一般來說, 泛型的作用就類似一個占位符, 或者說是一個參數, 可以讓我們把類型像參數一樣進行傳遞, 儘可能地復用代碼

我有個朋友, 在使用的過程中發現一個問題

IFace<object> item = new Face<string>(); // CS0266

public interface IFace<T>
{
    string Print(T input);
}
public class Face<T> : IFace<T>
{
    public string Print(T input) => input.ToString();
}

Q:   string 明明是 object 的子類, 為啥這樣賦值會報錯呢???
A:   因為 Face<string> 實現的是 IFace<string>, 而 IFace<string> 並不是 IFace<object> 的子類
Q:   但是 stringobject 的子類啊, IFace<string> 可不就是 IFace<object> 嗎?
A:   如果只論介面定義, 看起來確實是這樣的, 但是你要看內部實現的方法, IFace<string>Print 方法參數是 string, 但是 IFace<object>Print 參數是 object, 如果上面的賦值可以成立, 就意味著允許 Print(string input) 方法傳遞任意類型的對象, 這樣明顯是有問題的
Q:   但是我曾經看到過 IEnumerable<object> list = new List<string>(); 這個為什麼就可以
A:   這就要講到C#泛型里的逆變協變
Q:   細嗦細嗦

逆變協變

C#泛型中的逆變(in)協變(out)對於不常自定義泛型的開發來說(可能)是個很難理解的概念, 簡單來說其表現形式如下

逆變(in): I<子類> = I<父類>
協變(out): I<父類> = I<子類>

上面例子中提到的 IEnumerable<object> list = new List<string>(); 體現的是協變, 符合一般直覺, 整體上看起來就像是將子類賦值給基類

轉到 IEnumerable<> 的定義, 我們可以看到

public interface IEnumerable<out T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

泛型 T 之前加了協變的關鍵詞 out, 代表支持協變, 可以進行符合直覺且和諧的轉化

前編中提到的代碼例子不適用並且也不能改造成協變, 只適合使用逆變

相比於符合直覺且和諧協變, 逆變不符合直覺並且彆扭

IFace<string> item = new Face<object>();

public interface IFace<in T>
{
    string Print(T input);
}
public class Face<T> : IFace<T>
{
    public string Print(T input) => input.ToString();
}

這是一個逆變的例子, 與協變相似, 需要在泛型 T 之前加上關鍵詞 in

對比上方的協變, 逆變看起來就像是將基類賦值給子類, 但這其實符合里氏代換的

當我們調用 item.Print 時, 看起來允許傳入的參數為 string 類型, 而實際上最終調用的 Face<object>.Print 是支持 object 的, 傳入 string 類型的參數沒有任何問題

逆變協變的作用

逆變(in)協變(out)的作用就是擴展泛型的用法, 幫助開發者更好地復用代碼, 同時通過約束限制可能會出現的破壞類型安全的操作

逆變協變的限制

雖然上面講了逆變(in)協變(out)看起來是什麼樣的, 但我的那個朋友還是有些疑問

Q:   那我什麼時候可以用逆變, 什麼時候可以用協變, 這兩個東西用起來有什麼限制?
A:   簡單來說, 有關泛型輸入的用逆變, 關鍵詞是in, 有關泛型輸出的用協變, 關鍵詞是out, 如果介面中既有輸入又有輸出, 就不能用逆變協變
Q:   為什麼這兩個不能同時存在?
A:   協變的表現形式為將子類賦值給基類, 當進行輸出相關操作時, 輸出的對象類型為基類, 是將子類轉為基類, 你可以說子類是基類;
逆變的表現形式為將基類賦值給子類, 當進行輸入相關操作時, 輸入的對象為子類, 是將子類轉為基類, 這個時候你也可以說基類是子類;
如果同時支持逆變協變, 若先進行子類賦值給基類的操作, 此時輸出的是基類, 子類轉為基類並不會有什麼問題, 但進行輸入操作時就是在將基類轉為子類, 此時是無法保證類型安全的;
Q:   聽不懂, 能不能舉個例子給我?
A:   假設 IEnumerable<> 同時支持逆變協變, IEnumerable<object> list = new List<string>();進行賦值後, list中實際保存的類型是string, item.First()輸出類型為object, 實際類型是string, 此時說stringobject沒有任何問題, 協變可以正常發揮作用;
但是如果支持了逆變, 假設我們進行輸入類型的操作, item.Add() 允許的參數類型為 object, 可以是任意類型, 但是實際上支持string類型, 此時的object絕無可能是string
Q:   好像聽懂了一點了, 我以後慢慢琢磨吧

兩者的限制簡單總結就是

輸入的用逆變
輸出的用協變


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 俗話說的好:技能學了~就要用在自己喜歡得東西上!! 這我不得聽個話~我喜歡小姐姐,跳舞的小姐姐 這不得用python把小姐姐爬下來~嘿嘿嘿 採集網站 本期目標來自虎牙舞蹈區 開發環境 Python 3.8 Pycharm 模塊 requests re 基本流程 數據來源分析 確定採集內容是什麼? ( ...
  • 作者:袁首京 原創文章,轉載時請保留此聲明,並給出原文連接。 草堂南澗邊,有客嘯雲煙。 掃葉林風後,拾薪山雨前。 野橋通竹徑,流水入芝田。 琴月相親夜,更深戀不眠。 話說周世宗顯德年間,有位老先生,性情疏野,不以榮宦為意。一生遇見了很多人、經歷了許多事。可惜這些事我一件也不知道、這些人我一個也不曉得 ...
  • 整理編輯:阿秀 鏈接:https://www.nowcoder.com/discuss/1096078 學弟分享 我是一個杭州雙非的本科生,2022屆畢業之後進了某銀行的科技部工作,年包 20w+。 當時想著在銀行也算是一份安穩的工作,因此選擇了給錢最多的一個,想著自己走上了金融 + 科技的賽道。 ...
  • 教程簡介 Apache Commons DBUtils入門教程 - 從基本到高級概念的簡單簡單步驟熟悉Apache Commons DBUtils,其中包括概述,環境設置,第一個應用程式,基本CRUD示例,創建,讀取,更新,刪除查詢,DBUtils對象,QueryRunner ,AsyncQuery ...
  • 前言 一、人物簡介 第一位閃亮登場,有請今後會一直教我們C語言的老師 —— 自在。 第二位上場的是和我們一起學習的小白程式猿 —— 逍遙。 二、算數運算符簡介 C語言的算數運算符,是用來完成基本的算術運算的符號。 按操作數個數可分為一元運算符(含一個操作數)和二元運算符(含兩個操作數)。 一元運算符 ...
  • abstract 由abstract關鍵字修飾的類稱為抽象類,可以將某些類共有的行為抽象出來,形成約束,提高開發效率。 //抽象類 public abstract class Action{ //抽象方法,只有方法名字,沒有方法的實現 public abstract void doSth(); } ...
  • 一、說明 在SimpleAdmin1.0版本中,我將整體項目結構分為三大塊,分別為架構核心、業務模塊和應用服務。隨著1.0版本的封版,回去再看我之前的項目架構,也暴露了一些問題,比如在1.0版本中,Signalr和Mqtt只能二選一,這顯然是不科學的,因為這兩種雖然都可以作為消息通知,但是顯然可以有 ...
  • .NET是一種用於構建多種應用的免費開源開發平臺,可以使用多種語言,編輯器和庫開發Web應用、Web API和微服務、雲中的無伺服器函數、雲原生應用、移動應用、桌面應用、Windows WPF、Windows窗體、通用 Windows平臺 (UWP)、游戲、物聯網 (IoT)、機器學習、控制台應用、 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...