4

五分钟看完,彻底理解协变逆变 - BruceNeter

 1 year ago
source link: https://www.cnblogs.com/qwqwQAQ/p/17622254.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

其实这是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去看一下,为什么官方的就可以。

1306612-20230810223753847-416609663.png


我们发现官方在泛型前面多了一个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示意图

1306612-20230810231509747-1992789537.png
Out示意图

1306612-20230810233030775-315467689.png

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

1306612-20230810233549025-1490301923.png


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

1306612-20230810233641046-757849424.png

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

1.属性为啥可以用逆变协变?
属性不就是get/set方法。
2.为什么接口和委托可以用逆变协变,类不行?
拜托你找一下共同点,接口和委托的共同点,都是行为,也就是方法为核心。接口里不能有字段。这也印证了我说的逆变协变最终是为方法服务的。
之所以类不行,我大概理解是方法和实例是分开的,本身不和实例存储在一起,也不是每个实例一份,如果逆变和协变可以服务类,那么会出现同样的类型,但是每个实例内部的同一个字段的类型都不一样,这对于存储和类型安全都是问题。
3.逆变和协变有啥用?
当你...设计问题,我就有遇到,有时候用上能更加优雅或者灵活的写代码吧,看你吧,少年。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK