五分钟看完,彻底理解C#协变逆变

dotNET全栈开发

共 2890字,需浏览 6分钟

 ·

2023-08-17 00:15

前言

其实这是c#的老知识点了,但是发现同事对这个竟然还一知半解,就和他们讲解了下,顺便也回顾了下,同事我也把我对这个的全部理解,融化成几分钟的讲解,保证大家5分钟内全部理解,看不懂来打我。

协变、逆变 解决的问题

泛型类型转换

比如Person类是Student的父类,我们平时可以直接:

      Person A = new Student();
    

这是所谓的隐式转换,相信百分之999.99%的人都知道。

然后随着大家写代码越来越多,就会遇到这样的场景。

      //我有一个集合
//我手上有一批学生
IEnumerable<Student> students = new List<Student>();
//我要他们先做人
IEnumerable<Person> peoples = students;

第一次看到这种代码,其实哪怕你一点不知道协变,逆变,你也觉得这是一段正常不过的代码,因为每个学生都是人,都可以直接转成 这个类型,那我一批学生不就是一批人吗。

是的,你这样想绝对没错,不然微软怎么会能让你这样写没问题还编译通过呢?

但是如果我自己写一个:

      //定义一个工作的泛型接口
public interface IWork<T
{
            
}

实现类
public class Work<T> : IWork<T
{
            
}

//直接报错
IWork<Person> work = new Work<Student>();

现实给了我们当头一棒,这时候,我们应该找到 IEnumerable,选中然后狠狠的F12去看一下,为什么官方的就可以。

19c1b0780c4044b8291d4fe0413381eb.webp

我们发现官方在泛型前面多了一个out关键字。破案了~

现在我们在我们的代码中也加入out关键字

      public interface IWork<out T
{
            
}

public class Work<T> : IWork<T
{
            
}
IWork<Person> work = new Work<Student>();

OK~代码正常运行。

原则核心

这里开始我们挑战五分钟速通,如果按照正常博客上来先讲概念,别说五分钟了,可能大家也就迷迷糊糊地看完了,所以我们直接整活。

核心依据

正如数学的发展是从1+1=2作为开始,我们也需要一些真理来支撑我们讲下去。那么我们的核心依据就是:里氏替换——C#里,子类转父类可以直接隐式转换

就这么短,就完事了?对,记住就行!!!

Out/In 输入输出?

讲到这里,我们继续忽悠,out是啥?来个翻译!不就是输出吗?in是啥,不就是输入吗?那么带入一下,Out不就是返回值吗,In不就是入参吗。那不就是方法的特征么。(先假设,再假设)

In:那么根据核心依据,子类转父类可以直接转,入参如果限定是Person类型,那么你给我限定为Student或者任意的Person类型的派生类,我都是可以接受的,因为都是安全的,可以直接转换过来的。

这种从基类转向派生类的兼容,就是所谓的逆变。说白了,我让你给我一个人,你说不行,我给你找个学生,那肯定是满足需求的。

Out:Out代表的是返回值,根据核心依据,我返回的是Student类型,你说不行,你给我返回Person类型,那我不是笑开花了,我连Student都能返回,你让我返回父类,那我不是直接转就过去了,总归是类型安全的。

这种从派生类转向基类的兼容,就是所谓的协变。

说白了,我可以造个学生,结果你说给个人就行, 那不是so easy。

In示意图

44a5b3f0eed1402a509f0d12b5c9a80b.webp

Out示意图

6873ff5e3fb72034267b75e5c6e74783.webp

证明

好了,我们说了这么多,至少证明下In/Out是代表的入参和返回值吧?直接show you code: 当Out作为返回值时的泛型没有问题,但是入参就报错了

f80f7de892d6ead8c5ca54ae071237f9.webp

当In作为入参时的泛型没有问题,但是返回值就报错了

c047cbf524a5878b5b15320a1e330d08.webp

好了,这还需要再解释吗?最后我们总结下,逆变和协变就是让方法有了泛型类型上的转换能力,强化了方法的多态能力。

问题点

1、属性为啥可以用逆变协变?

属性不就是get/set方法。

2、为什么接口和委托可以用逆变协变,类不行?

拜托你找一下共同点,接口和委托的共同点,都是行为,也就是方法为核心。接口里不能有字段。这也印证了我说的逆变协变最终是为方法服务的。

之所以类不行,我大概理解是方法和实例是分开的,本身不和实例存储在一起,也不是每个实例一份,如果逆变和协变可以服务类,那么会出现同样的类型,但是每个实例内部的同一个字段的类型都不一样,这对于存储和类型安全都是问题。

3、逆变和协变有啥用?

你...设计问题,我就有遇到,有时候用上能更加优雅或者灵活的写代码吧,看你吧,少年。

转自:BruceNeter

链接:cnblogs.com/qwqwQAQ/p/17622254.html






3c9239acadc934f78a1c96f2797f1306.webp
回复   【关闭】 学永久关闭App开屏广告 回复 【删除】学自动检测那个微信好友删除、拉黑 回复   【手册】 获取3万字.NET、C#工程师面试手册 回复 【帮助】获取100+个常用的C#帮助类库 回复 【加群】加入DotNet学习交流群
浏览 26
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报