首页 > 编程语言 >C#泛型的逆变协变(个人理解)

C#泛型的逆变协变(个人理解)

时间:2023-04-04 10:48:16浏览次数:45  
标签:IFace string C# 子类 基类 逆变 协变 泛型

前编

一般来说, 泛型的作用就类似一个占位符, 或者说是一个参数, 可以让我们把类型像参数一样进行传递, 尽可能地复用代码

我有个朋友, 在使用的过程中发现一个问题

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:   好像听懂了一点了, 我以后慢慢琢磨吧

两者的限制简单总结就是

输入的用逆变
输出的用协变

标签:IFace,string,C#,子类,基类,逆变,协变,泛型
From: https://www.cnblogs.com/CollapseNav/p/17285595.html

相关文章

  • 快慢指针-leetcode-26
    题目描述:给定一个已经排序好的数组,删除重复的元素,使每个元素只出现一次,并返回新的数组长度。不要为另一个数组分配额外的空间,必须采用O(1)额外内存复杂度的原地算法来解决这个问题。示例1:输入:nums=[1,1,2]输出:length=2,nums=[1,2]解释:函数应该返回新的长度2,......
  • opencv-python 4.10.4. 反投影直方图
    理论它由MichaelJ.Swain,DanaH.Ballard在他们的论文“Indexingviacolorhistograms”中提出。用简单的话来说,它到底是什么?它用于图像分割或查找图像中感兴趣的对象。简单地说,它创建了与输入图像大小相同(但是是单一通道)的图像,其中每个像素对应于该像素属于对象的概率。在......
  • 第一推动|2023年VSCode插件最新推荐(54款)
    本文介绍前端开发领域常用的一些VSCode插件,插件是VSCode最重要的组成部分之一,本文列出了我自己在以往工作经验中积累的54款插件,个人觉得这些插件是有用或有趣的,根据它们的作用,我粗略的把它们分成了代码管理、文本和图片处理、前端框架和语言相关、提效和功能增强以及主题和图标等......
  • reactive
    reactivereactive系统有些特性成棒为低延时,高通工载.项目reactor和spring套装共事使开发亻建企业级reactive系统是响应,恢复,弹性,消息驱动的.什是reactive处理?reactive处理是范例使开发亻建非阻,异步app可拿捏背压(流控)为什用reactive处理?reactive系统更好使用当下处理......
  • docker 部署mongoDB集群与读写分离
    一.生成key文件需要注意集群中所有机器都需要用同一个文件,否则会出现验证失败的情况#生成keyopensslrand-base64756>/data/volume/mongodb/configdb/mongo.key#设置访问权限chmod400/data/volume/mongodb/configdb/mongo.key 二.启动MongoDB的docker容器d......
  • Windows - sfc scan
    C:\Windows\System32>sfc/scannowBeginningsystemscan.Thisprocesswilltakesometime.Beginningverificationphaseofsystemscan.Verification100%complete.WindowsResourceProtectionfoundcorruptfilesandsuccessfullyrepairedthem.Foronlin......
  • centos7/centos8 PHP7.2/php7.3/php7.4 以上版本 源码安装 编译
    yumupdate  1、安装依赖包[root@centos7_4~]#yum-yinstallphp-mcryptlibmcryptlibmcrypt-devel autoconf freetypegdlibmcryptlibpnglibpng-devellibjpeglibxml2libxml2-develzlibcurlcurl-develre2cnet-snmp-devellibjpeg-develphp-ldapopenl......
  • cmake get_filename_component
    get_filename_component(<var><FileName><mode>[BASE_DIR<dir>]var:outputValueFileName:inputValuemodeDIRECTORY=DirectorywithoutfilenameNAME=FilenamewithoutdirectoryEXT=Filenamelongestextension......
  • C. Place for a Selfie
    C.PlaceforaSelfieTheuniverseisacoordinateplane.Thereare$n$spacehighways,eachofwhichisastraightline$y=kx$passingthroughtheorigin$(0,0)$.Also,thereare$m$asteroidbeltsontheplane,whichwerepresentasopenupwardsparabo......
  • CentOS7 卸载mysql(YUM源方式)
     防止重装yum方式查看yum是否安装过mysqlyumlistinstalledmysql*如或显示了列表,说明系统中有MySQLyum卸载 根据列表上的名字 yumremovemysql-community-clientmysql-community-commonmysql-community-libsmysql-community-libs-compatmysql-commun......