王朝网络
分享
 
 
 

不变性、协变性和逆变性(Invariance, Covariance & Contravariance)

王朝学院·作者佚名  2016-05-20  
宽屏版  字体: |||超大  

不变性、协变性和逆变性(Invariance, Covariance & Contravariance)源码下载

一、里氏替换原则(Liskov Substitution PRinciple LSP)

我们要讲的不是协变性和逆变性(Covariance & Contravariance)吗?是的,没错。但先不要着急,在这之前,我们有必要再回味一下LSP。废话不多说,直接上代码:

1 namespace LSP 2 { 3 public class Bird 4 { 5 public virtual void Show() 6 { 7 Console.WriteLine("It's me, bird."); 8 } 9 }10 }

Bird 1 namespace LSP 2 { 3 public class Swan : Bird 4 { 5 public override void Show() 6 { 7 Console.WriteLine("It's me, swan."); 8 } 9 }10 }

Swan 1 namespace LSP 2 { 3 public class Program 4 { 5 static void Main(string[] args) 6 { 7 Bird bird = new Swan(); 8 bird.Show(); 9 Console.ReadLine();10 }11 }12 }

Program根据里氏替换原则,任何基类可以出现的地方,子类一定可以出现。

因为Swan类继承于Bird类,所以“Bird bird=new Bird();”中,我需要创建一个Bird对象,你给了我一个Swan对象是完全可行的。通俗地讲,我要你提供鸟类动物给我,你给我一只天鹅,当然没有问题。

然而,我们在调用bird的Show方法时,发生了什么呢?

Bird类和Swan类中都有Show方法,调用这个方法时,编译器是知道这个bird实际指向的Swan对象的。它会先查看Swan本身是不是有同签名的方法,如果有就直接调用。如果没有再往Swan的父类里查看,如果再没有,再往上面找,直到找到为止。如果最终也没有找到,就会报错。

所以,我们看到程序调用的是Swan的Show方法:"It's me, swan."

二、协变和逆变是什么?

关于这个,我们还是先看看官方的解释:

协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。

看了是不是有种“懂的依然懂,不懂的依然不懂的感觉”?

简单地说,

协变:你可以用一个子类对象去替换相应的一个父类对象,这是完全符合里氏替换原则的,和协(谐)的变。如:用Swan替换Bird。

逆变:你可以用一个父类对象去替换相应的一个父类对象,这貌似不符合里氏替原则的,不和协(谐)的逆变。如:用Bird替换Swan。

那么事实真的如此吗?协变是不是比逆变更合理?其实他们完全就是一回事,都是里氏替换原则的一种表现形式罢了。

三、不变性(Invariance)

我们知道:Bird bird=new Swan();是没有问题的。

那么对于泛型,List<Bird> birds=List<Swan>();是不是也OK呢?

No!

首先,因为.Net Framework只向泛型接口和委托提供了协变和逆变的便利。

再者,想要实现协变或逆变,也得在语法上注明out(协变)或in(逆变)。

对于这类不支持协变和逆变的情况,我们称为不变性(Invariance)。为了维持泛型的同质性(Homogeneity),编译器禁止将List<Swan>隐式或显式地转换为List<Bird>。

好了,重点来了!

为什么要这样?这样,很不方便。而且,看起来也不符合里氏替换原则。

简单地说,维持同质性,不允许这样的转换,还是为了编译正常。什么是编译正常,就是别给咱报错。

1 public class Program 2 { 3 public static void Main(string[] args) 4 { 5 List<object> obj = null; 6 List<string> str = null; 7 8 /* Error: 9 * Cannot implicitly convert type10 * 'System.Collections.Generic.List<string>' 11 * to 'System.Collections.Generic.List<object>'12 */13 14 //obj = str;15 16 Console.ReadLine();17 }18 }

VarianceList如代码注解的那样,“obj=str;”编译器会报错:

Error :Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<object>'

List<T>是微软提供给我们的,里面封闭太多东西,不方便分析,我们就自己动手来写一个泛型类Invariance<T>。

1 namespace Invariance 2 { 3 public class Invariance<T> 4 { 5 T Test(T t) 6 { 7 return default(T); 8 } 9 }10 }

Variance<T>写好了泛型类,我们再来试一试。

1 namespace Invariance 2 { 3 public class Program 4 { 5 public static void Main(string[] args) 6 { 7 Invariance<object> invarianceObj = new Invariance<object>(); 8 Invariance<string> invaricaceStr = new Invariance<string>(); 9 10 //invarianceObj = invaricaceStr;11 //invaricaceStr = invarianceObj;12 13 Console.ReadLine();14 }15 }16 }

Variance<T> Test"invarianceObj = invaricaceStr;"报错:

Error : Cannot implicitly convert type 'Invariance.Invariance<string>' to 'Invariance.Invariance<object>'

“invaricaceStr = invarianceObj;”报错:

Error : Cannot implicitly convert type 'Invariance.Invariance<object>' to 'Invariance.Invariance<string>'

讲到这么多报错,还是没讲到核心,为什么要报错。

我们可以假设,如果不报错,运行起来会是怎样:Invariance<T>类型参数T是在使用时,确定具体类型的。

先来说貌似符合里氏替换原则的情况,

Invariance<object> invarianceObj =new Invariance<string>();

用string替换object没有问题。但这个语句表达的不仅仅是用string来替换object,也表示用object来替换string。

关键在于类型参数,是在泛型类中使用的,我们不敢保证他是否于参数还是返回值。

如:Invariance<object> invarianceObj调用Test(object obj),传入的是自身的类型参数,而实际执行时,是执行实际指向的对象Invariance<string> invarianceStr的Test(string str)方法。很明显,Invariance<string> invariance的Test(string str)方法需要接收一个string类型的参数,得到却是一个object。这是不合法的。

那是不是反过来就可以了呢?

Invariance<string> invaricaceStr=new Invariance<object>();

这样,你实际执行方法时,需要一个object类型的参数,我给你一个string总没问题了吧。

OK,这样完全没有问题。

然而,不要忘了,方法可能不只是有参数,还可能有返回值。

参数:Invariance<string> invaricaceStr调用Test(string str),将string传给invarianceObj的Test(object obj)方法。目前为止,OK。

返回值:Invariance<string> invaricaceStr要求Test(string str)返回一个string对象。而实际执行方法的invarianceObj却只能保证返回一个object对象。NG!

看到了吧。这就是为什么.Net Framework要保持类型参数的同质性,而不允许T类型参数,哪怕从子类到父类或父类到子类的任何一种转换。

因为你只能保证参数或返回值,其中一项转换成功。

四、协变性(Covariance)

理解了为什么要坚持不变性,理解起协变性就容易多了。如果我能在泛型接口或者委托中保证,我的类型参数,只能外部取出,不允许外部传入。那么就不存在上面讲的将类型参数作为参数传入方法的情况了。

怎么保证?只需要在类型参数前加out关键字就可以了。

1 namespace Covariance2 {3

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有