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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...