c#: 协变和逆变深度解析

环境:

window 10
.netcore 3.1
vs2019 16.5.1

一、为什么要有协变?
首先看下面的代码:

还有下面的:

其实上面报错的是同一个问题,就是你无法用List<Fruit>指向List<Apple>!
我们的疑问在于,明明是一个盛放苹果的箱子,我们说它可以盛放水果怎么了???
下面我来说一下原因:

  • 首先,不能根据这个类的用途去判断,因为你无法保证List这个类一定是集合(List当然是集合,但如果是Person<T>呢,它是做什么的?只是盛放东西吗?)。
    其次,Apple继承自Fruit没错,但List<Apple>和List<Fruit>压根就没有继承的说法,它们是不同的类型(泛型参数类型不同也是不同的类型):

  • Console.WriteLine(typeof(List<Apple>) == typeof(List<Fruit>));输出为:false

所以,我们用List<Fruit>去表示List<Apple>引发报错很正常!!!
但是,从我们程序员角度来说,这样肯定不方便,那么有没有解决办法呢?
答案:有,它就是协变!


二、什么是协变?
首先,明确一下目的:我们想让List<Fruit> list = new List<Apple>();这类代码成立!(这行代码肯定不成立,我说的是这类代码)
想要达到我们的目的,肯定是要有规则的:

必须使用接口进行指向,不能使用类:

比如说:我们只能这么写IList<Fruit> list = new List<Apple>();(虽然这样写也报错),不能够这么写List<Fruit> list = new List<Apple>();

为什么不能使用类?因为类里面牵扯到的内容比较多,而下一条规则就说了:方法的入参不能使用泛型参数,所以为了尽量把这种约束的范围变小一点,我们也应该在接口上加规则约束而不是直接在类上(这一点是我猜的)。


这个接口的泛型参数只能用来做接口内方法的返回值,不能用作接口内方法的参数(在泛型参数前加out关键字实现):

这里从两方面说:
1.允许这个泛型参数做返回值:比如定义接口ITest<out T>,允许T作为接口内方法MethodA的返回值(T MethodA();)。在使用的时候,你用ITest<Fruit>指向ITest<Apple>,那么当调用ITest<Fruit>的方法MethodA的时候你得到的返回类型声明是Fruit,实际上你得到的返回类型是Apple,所以一点问题没有。
2.禁止这个泛型参数做方法的入参:比如定义接口ITest<out T>,允许T作为接口内方法MethodA的入参(void MethodA(T t);)。在使用的时候,你用ITest<Fruit>指向ITest<Apple>,那么当调用ITest<Fruit>的方法MethodA的时候你看到这个方法要求传入一个Fruit,所以你可能传一个orange(橙子,也继承了Fruit)进去,但人家实际上是ITest<Apple>,要求传入的是Apple,这样肯定说不通!所以泛型参数禁止做方法的入参!

上面说了规则,那么下面来一个实例:

可以看到,我们按照规则在ITest的泛型参数T上加了out后,整个程序腰不酸了、腿不疼了。
事实上,微软在集合的定义上已经考虑到了这一点,看一下IEnumerable的定义:

所以,我们像下面这样写也没有错:

讲到这里,我们可以说一下什么是协变了:
假如有两个类:A和AA,其中AA继承自A,如果此时有一个泛型接口IC<out T>,那么可以认为IC<A>能指向IC<AA>,即:IC<AA>和IC<A>的关系看着像AA和A的关系一样(只是看着像,并且能单方向转换,但不是继承!!!)。


三、什么是逆变?
逆变和协变是相对的,具体来说:

逆变的目的是:让List<Apple> test = new List<Fruit>();这类代码成立!(这行代码肯定报错,我说的是这类代码)
你一定认为这疯了,“说一个盛放水果的箱子盛放的是苹果”肯定不对。
但是我们看下面的实例:

上图中的代码是不是颠覆了你的认知?
好吧,这就是逆变:一个可以让你用ITest<Apple>去指向Test<Fruit>()的存在!
这里还是再说一下逆变的规则:

必须使用接口进行指向,不能使用类:

这一点和协变是一样的。


这个接口的泛型参数只能用来做接口内方法的入参,不能用作接口内方法的返回值(在泛型参数前加in关键字实现):

这里从两方面说:
1.允许这个泛型参数做方法的入参:比如定义接口ITest<in T>,允许T作为接口内方法MethodA的入参(void SetValue(T t);)。在使用的时候,你用ITest<Apple>指向ITest<Fruit>,那么当你调用ITest<Apple>的方法MethodA的时候你看到这个方法要求传入一个Apple,实际上人家是ITest<Fruit>,人家要求传入的是Fruit,所以这里一点问题没有。
2.禁止这个泛型参数做方法的返回值:比如定义接口ITest<in T>,允许T作为接口内方法MethodA的返回值(T GetValue();)。在使用的时候,你用ITest<Apple>指向ITest<Fruit>,那么当调用ITest<Apple>的方法MethodA的时候你得到的返回类型声明是Apple,但实际上人家是ITest<Fruit>,所以返回的是一个orange(橙子,也继承了Fruit)也说不定,所以你用Apple去接收这个返回值肯定不行的,所以泛型参数禁止做方法的返回值!

四、委托内的协变和逆变
委托中的泛型参数是天然就可以支持协变或逆变中的一种的!
对这句话的理解如下:

如果你这么定义委托:public delegate T GetValue<T>();,那么它天然支持协变(因为T只用来声明返回值),如下代码:

如果你这么定义委托:public delegate void SetValue<T>(T t);,那么它天然支持逆变(因为T只用来做入参),如下代码:

如果你这么定义委托,它既不支持协变,也不支持逆变:public delegate T Deal<T>(T t);(因为T即用来做入参也用来做返回值),如下代码:

其实,在委托中为了更好的表示泛型参数是支持协变还是逆变,最好是定义的时候就用out或in参数进行声明,比如:
public delegate T GetValue<out T>();//支持协变
public delegate void SetValue<in T>(T t);//支持逆变
微软在Func、Action系列委托中已经为我们做了示范:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309979.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[设计模式]抽象工厂模式

抽象工厂模式针对的是产品族&#xff0c;而不是产品等级结构。 产品族:同一产地或者同一产商&#xff0c;功能不同。 产品等级:功能相同&#xff0c;产地或者厂商不同。 代码如下: #include <iostream> using namespace std;class AbstractApple { public:virtual vo…

Too Many Segments (hard version) CodeForces - 1249D2(贪心+容器vector+set)

题目 给多组线段&#xff0c;而每一个点的覆盖次数不超过K&#xff0c;每次可去除一个线段&#xff0c;问最少去多少线段以及线段的位置。 The only difference between easy and hard versions is constraints. You are given nn segments on the coordinate axis OX. Segme…

.net core HttpClient 使用之掉坑解析(一)

一、前言在我们开发当中经常需要向特定URL地址发送Http请求操作&#xff0c;在.net core 中对httpClient使用不当会造成灾难性的问题&#xff0c;这篇文章主要来分享.net core中通过IHttpClientFactory 工厂来使用HttpClient的正确打开方式。二、HttpClient使用中的那些坑2.1 错…

linux常用命令 java,Java工程在Linux常用命令

Java Web工程 在Linux下操作常用命令cd ../ 退出当前目录,前往父文件夹cd ezoffice 进入ezoffice文件夹ls 查看目录ps -ef|grep java 查看JAVA进程ps -aux |grep tomcat 查看tomcat进程 的进程号kill -9 12222 杀死ID为12222进程nohup ./startup.sh & 执行startup.sh&…

[设计模式]单例模式(懒汉式,饿汉式)

实现单例步骤: 1.构造函数私有化。 2.增加静态私有的当前类的指针变量。 3.提供静态对外接口&#xff0c;可以让用户获得单例对象。 单例 分为&#xff1a; 1.懒汉式 2.饿汉式 懒汉式 代码如下: class Singleton_lazy { public:static Singleton_lazy *getInstance(){if (pS…

By Elevator or Stairs? CodeForces - 1249E(动态规划)

题意 n层楼&#xff0c;a[i] (0<i<n)表示从 i 楼到 i 1 楼走楼梯的时间&#xff0c;b[i] (0<i<n)表示从 i 楼到 i 1 楼乘电梯的时间&#xff0c;其中每一次乘电梯需要等待 k 时间&#xff0c;楼梯和电梯一次均可上从 x 楼上升到 y 楼 ( y ! x )&#xff0c;即一…

我擦!没想到你们都是这样 “劝退” 员工的!

前几天&#xff0c;我的一个好哥们在微信上跟我吐槽&#xff0c;说这波疫情对经济的影响实在太大了。他说在往年&#xff0c;这个时候跳槽应该开始冒头了&#xff0c;而今年从春节到现在&#xff0c;除了少数几个被裁员之外&#xff0c;200多人的技术团队几乎就没一个主动提离职…

php post nginx 400,Nginx静态文件响应POST请求 提示405错误的解决方法

例1&#xff1a;用linux下的curl命令发送POST请求给Apache服务器上的HTML静态页[rootlocalhost ~]# curl -d 111 https://www.jb51.net/index.html405 Method Not AllowedMethod Not AllowedThe requested method POST is not allowed for the URL /index.html.Apache/1.3.37 S…

Phone List POJ - 3630(字典树模板题)

题意 给定 n个长度不超过 10的数字串&#xff0c;问其中是否存在两个数字串S&#xff0c;T &#xff0c;使得 S是 T的前缀&#xff0c;多组数据。 题目 Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of anothe…

开源最大的谎言是什么?

一天前&#xff0c;网友 niksmac 在 Hacker News 上提出了这样一个问题&#xff1a;“开源最大的谎言是什么”&#xff1f;由此引发了诸多讨论。从其他网友的回复来看&#xff0c;他们主要将焦点集中在开源的安全性、使用成本、商业化、开源精神及道德等方面。收到最多回复的网…

唯品会php接口,唯品会链接生成联盟链接 - 唯品会API免费API接口-唯品会API开放API接口-云商数据(www.ecapi.cn)...

{"code":200,"data":{"list":[{"noEvokeUrl":"https://t.vip.com/xxxxx?&wq1","vipQuickAppUrl":"hap://app/com.VIP.VIPQuickAPP/pages/index?targetpages/product/detail&params{"productI…

[设计模式]代理模式

代理模式: 为其他对象提供一种代理以控制对这个对象的访问。 在某些情况下&#xff0c;一个对象不适合或者不能直接引用另一个对象&#xff0c;而代理对象可以在客户端和目标对象之间起到中介的作业。 代码如下: #include <iostream> using namespace std;//共有接口 …

Minimize the Permutation CodeForces - 1256(贪心)

题意&#xff1a; q次询问&#xff0c;每次询问给你长度为n的排列&#xff0c;然后你每次可以选择一个位置i和i1的数字进行交换。但是每个位置只能交换一次&#xff0c;问你反转若干次后&#xff0c;这个排列最小是多少&#xff1f; 题目&#xff1a; You are given a permu…

IO 模型知多少 | 代码篇

引言之前的一篇介绍IO 模型的文章IO 模型知多少 -- 理论篇比较偏理论&#xff0c;很多同学反应不是很好理解。这一篇咱们换一个角度&#xff0c;从代码角度来分析一下。socket 编程基础开始之前&#xff0c;我们先来梳理一下&#xff0c;需要提前了解的几个概念&#xff1a;soc…

[设计模式]外观模式

外观模式:为一组具有类似功能的类群&#xff0c;比如类库&#xff0c;子系统等等&#xff0c;提供一个一致的简单的界面。 代码如下: #include <iostream> using namespace std;class Television { public:void on(){cout << "Tv on" << endl;}v…

Keywords Search HDU - 2222(AC自动机模板)

题意&#xff1a; 给定 n个长度不超过 50的由小写英文字母组成的单词准备查询&#xff0c;以及一篇文章&#xff0c;问&#xff1a;文中出现了多少个待查询的单词。多组数据。 题目&#xff1a; In the modern time, Search engine came into the life of everybody like Go…

php fpm 调试模式,调试 – nginx php-fpm xdebug netbeans只能启动一个调试会话

在过去,我使用apache mod_PHP xdebug netbeans进行开发我的网站(服务器是我的本地机器,运行Debian Squeeze),很高兴 – xdebug工作正常,调试会话可以随时启动和停止,当我需要时它.但是,当我转移到Nginx PHP_fpm xdebug netbeans时,我遇到了一些调试问题.>我的调试会话可能会…

介绍一个基于 .NET 的船的新 PHP SDK + Runtime: PeachPie

前言这几天想基于 .NET Core 搞一个自己的博客网站&#xff0c;于是在网上搜刮各种博客引擎&#xff0c;找到了这些候选&#xff1a;Blogifier、Miniblog 以及 edi 写的 Moonglade。Blogifier&#xff1a;这是前端是个 Angular SPA 应用&#xff0c;不利于 SEO&#xff0c;同时…

[设计模式]适配器模式

适配器模式:将一个类的接口转换成客户希望的另外一个接口&#xff0c;使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 &#xff08;将已经写好的&#xff0c;但是不符合需求的接口&#xff0c;转换成目标接口&#xff09; 代码如下: #include <iostream>…

数位dp总结 之 从入门到模板(stO)

#转载自https://blog.csdn.net/wust_zzwh/article/details/52100392 基础篇 数位dp是一种计数用的dp&#xff0c;一般就是要统计一个区间[le,ri]内满足一些条件数的个数。所谓数位dp&#xff0c;字面意思就是在数位上进行dp咯。数位还算是比较好听的名字&#xff0c;数位的含义…