王朝网络
分享
 
 
 

[C#]浅谈协变与逆变

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

[C#]浅谈协变与逆变看过几篇说协变与逆变的博客,虽然都是正确无误的,但是感觉都没有说得清晰明了,没有切中要害。那么我也试着从我的理解角度来谈一谈协变与逆变吧。

什么是协变与逆变

MSDN的解释:https://msdn.microsoft.com/zh-cn/library/dd799517.aspx

协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型。泛型类型参数支持协变和逆变,可在分配和使用泛型类型方面提供更大的灵活性。

一开始我总是分不清协变和逆变,因为MSDN的解释实在是严谨有余而易读不足。其实从中文的字面上来理解这两个概念就挺容易的了:

"协变"即"协调的转变","逆变"即"逆向的转变"。

为什么说"能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型"是协调的,而"能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型"是逆向的呢,看这两行代码:

object o = "";string s = (string) o;

string类型到object类型,也就是派生类到基类,是可以隐式转换的,因为任何类型向基类的转换都是类型安全的,所以认为这一转变是协调的。object类型到string类型,也就是基类到派生类,就只能是显式转换,因为对象o的实际类型不一定是string,强制转换不是类型安全的,所以认为这一转变是逆向的。

再看协变与逆变的常见场合:

IEnumerable<object> o = new List<string>();//协变Action<string> s = new Action<object>((arg)=>{...});//逆变

上例的泛型参数就是分别发生了协调的与逆向的转变。

协变与逆变的作用对象

从定义中可以看到,协变与逆变都是针对的泛型参数,而且

在.NET Framework 4中,Variant类型参数仅限于泛型接口和泛型委托类型。

为什么是接口和委托?先看IEnumerable<T>和Action<T>的声明:

public interface IEnumerable<out T> : IEnumerable{ new IEnumerator<T> GetEnumerator();}public delegate void Action<in T>(T obj);

IEnumerable中的out关键字给泛型参数提供了协变的能力,Action中的in关键字给泛型参数提供了逆变的能力。这里的out和in是相对于谁的入和出?不是相对于接口和委托,而是相对于方法体!看它们的实现:

class MyEnumerable<T> : IEnumerable<T>{ public IEnumerator<T> GetEnumerator() { yield return default(T); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}Action<string> myAction = new Action<object>( (o) => { Console.WriteLine(o.ToString()); });

这样是不是能看出来泛型参数是怎么入和出的了?那么接口和委托,它们和方法是什么关系呢,它们两个之间又是什么关系,以下纯属个人理解:

接口类型定义了一组方法签名,委托类型定义了一个方法结构(方法签名刨除方法名)。接口实例和委托实例都包含了一组方法入口。

综上所述,协变与逆变的作用对象是方法体中的泛型参数。

为什么允许协变与逆变

协变和逆变都是类型发生了转换,一旦涉及到类型转换当然就要想类型安全的问题。协变和逆变之所以可以正常的运转,就是因为这里所涉及到的所有类型转换都是类型安全的!回头看最开始的四行代码:

1 object o1 = "";//类型安全2 string s1 = (string) o1;//非类型安全3 IEnumerable<object> o2 = new List<string>();//协变4 Action<string> s2 = new Action<object>((arg)=>{...});//逆变

显然第二行的object到string是非类型安全的,那为什么第四行的object到string就是类型安全的呢?结合上一个方法体的示例,来看这段代码:

1 Action<List<int>> myAction = new Action<IList<int>>(2 (list) =>3 {4 Console.WriteLine(list.Count);5 });6 myAction(new List<int> {1, 2, 3});

第一行貌似是把IList转换成了List,但是实际上是这样的:第六行传入的实参是一个List,进入方法体,List被转换成了IList,然后使用了IList的Count属性。所以传参的时候其实发生的是派生类到基类的转换,自然也就是类型安全的了。

List<string>到IEnumerable<object>的协变其实也是类似的过程:

1 IEnumerable<Delegate> myEnumerable = new List<Action> 2 { 3 new Action(()=>Console.WriteLine(1)), 4 new Action(()=>Console.WriteLine(2)), 5 new Action(()=>Console.WriteLine(3)), 6 }; 7 foreach (Delegate dlgt in myEnumerable) 8 { 9 dlgt.DynamicInvoke();10 }

实参是三个Action,调用的是Delegate的DynamicInvoke方法,完全的类型安全转换。

最后想说的是,所有死记硬背来的知识,都远远不如充分理解的知识来得可靠。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝网络 版权所有