C#协变逆变
文章目录
- 协变
- 协变接口的实现
- 逆变
- 里氏替换原则
协变
协变概念令人费解,多半是取名或者翻译的锅,其实是很容易理解的。
比如大街上有一只狗,我说大家快看,这有一只动物!这个非常自然,虽然动物并不严格等于狗,但不会有人觉得我说的不对,把狗变成动物就是协变,C#也支持这个:
// C#6顶级语句
Dog dog= new Dog();
Animal animal= dog;interface Animal
{}class Dog : Animal
{}
那么接下来,大街上有一群狗,我说有一群动物,按理说也是对的,但看样子C#不这么认为
List<Dog> dogLst = new List<Dog>();
List<Animal> aniLst = dogLst; //飙红飙红飙红了
interface Animal {}
class Dog : Animal {}
原因其实很容易理解,毕竟在上述的代码中,写了Dog:Animal,即声明了狗是动物的子类,但是并没有写List<Animal> : List<Dog>,换言之,从来没有声明过一群狗是一群动物的子类。
但是,如果不用List,而用其父类IEnumerable,写成下面这样,就又不报错了。
List<Dog> dogLst = new List<Dog>();
IEnumerable<Animal> aniLst = dogLst;
换言之,C#承认List<Dog>是IEnumerable<Animal>的子类,个中差别,只需一览源码,就会知晓:
public interface IEnumerable<out T> : IEnumerable
public class List<T> : ..., IEnumerable<T>, ...
IEnumerable无非比List多了一个out参数,有了这个参数,就拥有了协变的功能,从而当U是T的子类时,可以支持IEnumerable<U>到IEnumerable<T>的转换。
在官方文档中,指明了具有out关键字的泛型接口包括IEnumerable<T>, IEnumerator<T>, IQueryable<T>和IGrouping<TKey,TElement>。
协变接口的实现
协变和逆变目前只能在泛型接口和委托中使用,下面新建一个泛型接口,并使用关键字out。由于使用.Net6.0的顶级语句,所以接口和类的声明放在后面。
IOut<string> outStr = new Out();
IOut<object> outObj = outStr;
Console.WriteLine(outObj.getName());interface IOut<out T>
{T getName();
}class Out : IOut<string>
{public string getName(){return GetType().Name;}
}
编译运行,最后输出Out,即outObj尽管在声明的时候用的是IOut<object>,但在IOut的out修饰符的作用下,成功让IOut<object>变成了IOut<string>的父类,得以顺利调用Out中的方法。
那么接下来,如果想让getName更加完备一些,例如要求实现getName(T name)这样的功能,那么经out修饰的协变接口就无能为力了,像下面这样的写法果然被无情地飙红了
interface IOut<out T>
{void getName(T name);
}
逆变
VS作为宇宙顶级IDE,协变逆变十分拎得清,上述代码在飙红的同时,直接给出如下错误
变型无效: 类型参数“T”必须是在“IOut.getName(T)”上有效的 逆变式。“T”为 协变。
换言之,如果想让泛型接口可以输入泛型参数,那么需要用到逆变,具体写法如下,其中修饰符in表示逆变
IIn<object> inObj = new In();
IIn<string> inStr = inObj;
inStr.getName("in");interface IIn<in T>
{void getName(T name);
}class In : IIn<object>
{public void getName(object name){Console.WriteLine(name);}
}
逆变和协变最大的不同,并非in和out这两个修饰符的字数,而是整个替换逻辑发生了变化,上述代码中,实际上是作为子类的string调用了通过父类object作为参数定义的函数。
里氏替换原则
在具体实现了协变与逆变之后,总觉得那里怪怪的,最怪的其实还是下面这行代码的错误
错错错错错错错错错错错错错错错错错错错错错错错错错错
interface IOut<out T>
{void getName(T name);
}
而且可以想象,与之相对应的下面的逆变代码也是不对的
错错错错错错错错错错错错错错错错错错错错错错错错错错
interface IOut<in T>
{T getName();
}
接下来复盘一下产生这种现象的原因,为了破除命名带来的困扰,接下来考虑泛型接口I<T>,其中有一个函数T test(T t)。现有两个特定的继承自泛型接口I<A>和I<B>的类,假设I<A>要调用I<B>中的方法,那么其流程如下
A I<A>.test(A t),即输入一个A类型的参数- 将这个
A类型的参数t,传入到B I<B>.test(B t)。由于I<B>要求输入B类型的参数,所以要求A可以转换为B类型。 B I<B>.test(B t)计算完毕,返回一个B类型的参数- 这个
B类型的参数又被返回给最初的调用者A I<A>.test,而这时I<A>的函数最终将返回一个A类型的参数,换言之,在这个步骤,要求B可以转换为A。
A能转为B,然后还得B能转为A,同时A和B还不相等,这显然是不可能的。
所以逆变和协变分别实现了第2步和第4步。
如果I<A>想要调用I<B>test(B t)中的函数,那么A类型必须可以转成B类型。正如string可以转为object一样,此即逆变,用in修饰,其作用场合为子类调用父类中的方法。
如果I<A>想要调用B I<B>test(),那么作为返回值的B类型必须可以转化为A类型,此即协变,用out修饰,正是父类调用子类的方法。
协变和逆变的统一之处在于,二者都严格遵循这子类可以转变为父类的规则,此即里氏替换。这是1987年,芭芭拉·利斯科夫提出的,她也是2008年图灵奖得主。
在协变逆变的过程中,对里氏替换的遵循主要表现在当子类方法重载父类方法时
- 方法的输入参数要更加宽松,此即逆变(
IOut<object>调用IOut<string>,object比string更宽松) - 方法的返回值要更加严格,此即协变(
IOut<string>调用IOut<object>,string比object更严格)
相关文章:
C#协变逆变
文章目录协变协变接口的实现逆变里氏替换原则协变 协变概念令人费解,多半是取名或者翻译的锅,其实是很容易理解的。 比如大街上有一只狗,我说大家快看,这有一只动物!这个非常自然,虽然动物并不严格等于狗…...
算法设计与分析期末考试复习(四)
贪心算法(Greedy Algorithm) 找零钱问题 假设有4种硬币,面值分别为:二角五分、一角、五分和一分,现在要找给顾客六角三分钱,如何找使得给出的硬币个数最少? 首先选出1个面值不超过六角三分的最…...
qsort函数排序数据 and 模拟实现qosrt函数(详解)
前言:内容包括使用库函数qsort排序任意类型的数据,模拟实现qsort函数(冒泡排序的逻辑) 我们先了解qsort函数的语法:qsort函数默认按照升序排序数据 void qsort (void* base, size_t num, size_t size,int (*compar)(…...
Mysql视图,存储过程,触发器,函数以及Mysql架构
一,视图视图是基于查询的一个虚拟表 , 也就是将sql语句封装起来, 要用的时候直接调用视图即可, select语句查询的表称为基表, 查询的结果集称为虚拟表, 基本表数据发生了改变, 那么视图也会发生改变, 使用视图就是为了简化查询语句.1.CREATE VIEW view_admin AS SELECT * FROM…...
什么是线程死锁?如何解决死锁问题
死锁,一组互相竞争的资源的线程之间相互等待,导致永久阻塞的现象。 如下图所示: 与死锁对应的,还有活锁,是指线程没有出现阻塞,但是无限循环。 有一个经典的银行转账例子如下: 我们有个账户类…...
C语言几种判断语句简述
C 判断 判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。 C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 fals…...
【python学习笔记】:SQL常用脚本(二)
11、四舍五入ROUND函数 ROUND ( numeric_expression , length [ ,function ] ) function 必须为 tinyint、smallint 或 int。 如果省略 function 或其值为 0(默认值),则将舍入 numeric_expression。 如果指定了0以外的值,则将截…...
【Linux】进程地址空间
文章目录🎪 进程地址空间🚀1.写时拷贝与虚拟地址🚀2.地址空间引入🚀3.地址空间的意义⭐3.1 虚拟地址寻址⭐3.2 虚拟地址意义🎪 进程地址空间 地址空间(address space)表示任何一个计算机实体所…...
Qt音视频开发17-vlc内核回调拿图片进行绘制
一、前言 在众多播放器中,支持的种类格式众多,并支持DVD影音光盘,VCD影音光盘及各类流式协议,提供了sdk进行开发,这点是至关重要的,尽管很多优秀的播放器很牛逼,由于没有提供sdk第三方开发&…...
安装配置DHCP
本次实验采用CentOS71.检查在安装DHCP之前先使用rpm命令查看系统中已有的DHCP软件包rpm -qa | grep dhcp由此可知,系统中尚未安装DHCP软件包2.安装我们可以使用yum命令为系统安装DHCP软件包yum -y install dhcp安装完成后再次检查可以看到DHCP软件包3.配置dhcp配置文…...
MarkDown中写UML图的方法
目录序UML图之顺序图顺序图的四个要素关于消息箭头的语法Mermaid中顺序图的简单例子样例用小人表示对象为对象设置别名激活对象UML图之类图类图中常见的关系关于不同类型关系的语法Mermaid中类图的简单例子样例类定义的两种方式为类定义成员双向关系的表示多重性关系的表示UML之…...
Axure8设计—动态仪表盘
本次分享的的案例是Axure8制作的动态仪表盘,根据设置的数值,仪表盘指针旋转到相应的值位置 预览地址:https://2qiuwg.axshare.com 下载地址:https://download.csdn.net/download/weixin_43516258/87502161 一、制作原型 1、首先创建空白页…...
【C++】类和对象的六个默认成员函数
类的6个默认成员函数构造函数概念特性析构函数概念特性拷贝构造函数概念特征拷贝构造函数典型调用场景:赋值运算符重载运算符重载赋值运算符重载取地址及const取地址操作符重载类的6个默认成员函数 到底什么是类的6个默认成员函数呢?相信大家一定对此怀…...
4、算法MATLAB---认识矩阵
认识矩阵1、矩阵定义和基本运算1.1 赋值运算符:1.2 等号运算符:1.3 空矩阵1.4 一行一列矩阵1.5 行矩阵(元素用空格或逗号分隔)1.6 列矩阵(分号表示换行)1.7 m行n列的矩阵:行值用逗号间隔&#x…...
vue3+rust个人博客建站日记2-确定需求
反思 有人说过我们正在临近代码的终结点。很快,代码就会自动产生出来,不需要再人工编写。程序员完全没用了,因为商务人士可以从规约直接生成程序。 扯淡!我们永远抛不掉代码,因为代码呈现了需求的细节。在某些层面上&a…...
Linux安装云原生网关Kong/KongA
目录1 概述2 创建服务器3 安装postgres4 安装kong5 安装node6 安装KONGA1 概述 Kong Kong是一款基于OpenResty(NginxLua模块)编写的高可用、易扩展的开源API网关,专为云原生和云混合架构而建,并针对微服务和分布式架构进行了特别…...
Vue学习笔记(2)
2.1 事件处理 2.1.1 事件监听器 JavaScript:通过获取DOM对象再往DOM对象上使用addEventListener注册监听事件 const btn document.querySelector(#my-button) btn.addEventListener(click, function() {alert(点击事件!) })jQuery:通过$选择器绑定对象…...
2023年三月份图形化四级打卡试题
活动时间 从2023年3月1日至3月21日,每天一道编程题。 本次打卡的规则如下: 小朋友每天利用10~15分钟做一道编程题,遇到问题就来群内讨论,我来给大家答疑。 小朋友做完题目后,截图到朋友圈打卡并把打卡的截图发到活动群…...
Python操作Excel
Python中对Excel文件的操作包括:读、写、修改。如果要对其进行如上的操作需要导入Python的第三方模块:xlrd、xlwd、xlutils,其分别对应Python的读、写、修改的操作 一、安装Python的第三方模块 二、操作Excel的基本步骤 1、导入响对应的模…...
Codeforces Round #853 (Div. 2) C. Serval and Toxel‘s Arrays【统计次数,算贡献】
链接 传送门 分析 这道题想法其实很简单,样例的计算方法一定要看懂。以样例1为例,根据他的操作方法可以得到两个新的数组,和一个原来的数组,总共三个数组。 1 2 3 4 2 3 4 5 3 他们两两配对去重,求出总的value。由于每…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
