逆变(contravariant)与协变(covariant)是C#4新增的概念,许多书籍和博客都有讲解,我觉得都没有把它们讲清楚,搞明白了它们,可以更准确地去定义泛型委托和接口,这里我尝试画图详细解析逆变与协变。

变的概念

我们都知道.Net里或者说在OO的世界里,可以安全地把子类的引用赋给父类引用,例如:

1

2

3

//父类 = 子类

string str = "string";

object obj = str;//变了

而C#里又有泛型的概念,泛型是对类型系统的进一步抽象,比上面简单的类型更高级,把上面的变化体现在泛型的参数上就是我们所说的逆变与协变的概念。通过在泛型参数上使用in或out关键字,可以得到逆变或协变的能力。下面是一些对比的例子:

协变(Foo<父类> = Foo<子类> ):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

//泛型委托:

public delegate T MyFuncA<T>();//不支持逆变与协变

public delegate T MyFuncB<out T>();//支持协变

MyFuncA<object> funcAObject = null;

MyFuncA<string> funcAString = null;

MyFuncB<object> funcBObject = null;

MyFuncB<string> funcBString = null;

MyFuncB<int> funcBInt = null;

funcAObject = funcAString;//编译失败,MyFuncA不支持逆变与协变

funcBObject = funcBString;//变了,协变

funcBObject = funcBInt;//编译失败,值类型不参与协变或逆变

//泛型接口

public interface IFlyA<T> { }//不支持逆变与协变

public interface IFlyB<out T> { }//支持协变

IFlyA<object> flyAObject = null;

IFlyA<string> flyAString = null;

IFlyB<object> flyBObject = null;

IFlyB<string> flyBString = null;

IFlyB<int> flyBInt = null;

flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变

flyBObject = flyBString;//变了,协变

flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变

//数组:

string[] strings = new string[] { "string" };

object[] objects = strings;

逆变(Foo<子类> = Foo<父类>)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public delegate void MyActionA<T>(T param);//不支持逆变与协变

public delegate void MyActionB<in T>(T param);//支持逆变

public interface IPlayA<T> { }//不支持逆变与协变

public interface IPlayB<in T> { }//支持逆变

MyActionA<object> actionAObject = null;

MyActionA<string> actionAString = null;

MyActionB<object> actionBObject = null;

MyActionB<string> actionBString = null;

actionAString = actionAObject;//MyActionA不支持逆变与协变,编译失败

actionBString = actionBObject;//变了,逆变

IPlayA<object> playAObject = null;

IPlayA<string> playAString = null;

IPlayB<object> playBObject = null;

IPlayB<string> playBString = null;

playAString = playAObject;//IPlayA不支持逆变与协变,编译失败

playBString = playBObject;//变了,逆变

来到这里我们看到有的能变,有的不能变,要知道以下几点:

  • 以前的泛型系统(或者说没有in/out关键字时),是不能“变”的,无论是“逆”还是“顺(协)”。
  • 当前仅支持接口和委托的逆变与协变 ,不支持类和方法。但数组也有协变性。
  • 值类型不参与逆变与协变。

那么in/out是什么意思呢?为什么加了它们就有了“变”的能力,是不是我们定义泛型委托或者接口都应该添加它们呢?

原来,在泛型参数上添加了in关键字作为泛型修饰符的话,那么那个泛型参数就只能用作方法的输入参数,或者只写属性的参数,不能作为方法返回值等,总之就是只能是“入”,不能出。out关键字反之。

当尝试编译下面这个把in泛型参数用作方法返回值的泛型接口时:

1

2

3

4

public interface IPlayB<in T>

{

    T Test();

}

出现了如下编译错误:

错误    1    方差无效: 类型参数“T”必须为“CovarianceAndContravariance.IPlayB<T>.Test()”上有效的 协变式。“T”为 逆变。

到这里,我们大致知道了逆变与协变的相关概念,那么为什么把泛型参数限制为in或者out就可以“变”呢?下面尝试画图解释原理。

协变不是理所当然的,逆变也没有“逆”

我们先来看看不支持逆变与协变的泛型,把子类赋给父类,再执行父类方法的具体流程,对于这样一个简单的例子的Test方法:

1

2

3

4

5

6

7

8

9

10

public interface Base<T>

{

    T Test(T param);

}

public class Sub<T> : Base<T>

{

    public T Test(T param) { return default(T); }

}

Base<string> b = new Sub<string>();

b.Test("");

它实际的流程是这样的:

即调用父类的方法,其实实际是调用子类的方法。可以看到,这个方法能够安全的调用,需要两个条件:1.变式(父)的方法参数能安全转为原式(子)的 参数;2.原式(子)的返回值能安全的转为变式的返回值。不幸的是参数的流向跟返回值的流向是相反的,所以对于既是in,又是out的泛型参数来说,肯定 是行不通的,其中一个方向必然不能安全转换的。例如,对上面的例子,我们尝试“变”:

1

2

3

4

Base<object> BaseObject = null;

Base<string> BaseString = null;

BaseObject = BaseString;//编译失败

BaseObject.Test("");

这里的“实际流程”如下,可以看到,参数那里是object是不能安全转换为string,所以编译失败:

看到这里如果都明白的话,我们不难得到逆变与协变的”实际流程图”(记住,它们是有in/out限制的):

可以看到,从”实际流程图”来看,逆变根本没有“逆”,都离不开只能安全地把子类的引用赋给父类引用这个根本。

来到这里应该基本理解逆变与协变了,不过装配脑袋的这篇文章有个更高级的问题,原文也有解答,这里我用上面画图的方式去理解它。

图解逆变与协变的相互作用

问题的提出,你知道那个正确吗?

1

2

3

4

5

6

7

8

9

10

11

public interface IBar<in T> { }

//应该是in

public interface IFoo<in T>

{

    void Test(IBar<T> bar);

}

//还是out

public interface IFoo<out T>

{

    void Test(IBar<T> bar);

}

答案是,如果是in的话,会编译失败,out才正确(当然不要泛型修饰符也能通过编译,但IFoo就没有协变能力了)。这里的意思就是说,一个有协 变(逆变)能力的泛型(IBar),作为另一个泛型(IFoo)的参数时,影响到了它(IFoo)的泛型的定义。乍一看以为是in的其中一个陷阱是T是在 Test方法的参数里的,所以以为是in。但这里Test的参数根本不是T,而是IBar<T>。

我们画个图来理解它。既然out可以通过,那么它的“协变流程图”应该如下:

图跟前面那些大致一样,但理解它要跟问题相反(上面问题是先定义好IBar,再去定义IFoo)。1.我们定义好一个有协变能力的IFoo,这是前 提。2.可以推出,上面的流程是成立的。3.这个流程重点是参数流向,要使整个流程成立,就必须使IBar<string> = IBar<object>成立,这不就是逆变吗?整个结论就是,有协变能力的IFoo要求它的泛型参数(IBar)有逆变能力。其实根据上面的箭头也可以理解,因为原式和变式的变向跟参数的变向是相反的,导致了它们要有相反的能力,这就是装配脑袋文章说的:方法参数的协变-反变互换原则。根据这个原理,也很容易得出,如果Test方法的返回值是IBar<T>,而不是参数,那么就要求IBar<T>要有协变能力,因为返回值的箭头与原式和变式的变向的箭头是同向的。

The End!

转自:http://www.cnblogs.com/lemontea/archive/2013/02/17/2915065.html

【转】逆变与协变详解相关推荐

  1. 协变逆变java_Java中的逆变与协变

    什么是逆变与协变 协变(Covariance) 如果B是A的子类,并且F(B)也是F(A)的子类,那么F即为协变 逆变(Contravariance) 如果B是A的子类,并且F(B)成了F(A)的父类 ...

  2. C#高级语法之泛型、泛型约束,类型安全、逆变和协变(思想原理)

    一.为什么使用泛型? 泛型其实就是一个不确定的类型,可以用在类和方法上,泛型在声明期间没有明确的定义类型,编译完成之后会生成一个占位符,只有在调用者调用时,传入指定的类型,才会用确切的类型将占位符替换 ...

  3. 第五节:泛型(泛型类、接口、方法、委托、泛型约束、泛型缓存、逆变和协变)

    一. 泛型诞生的背景 在介绍背景之前,先来看一个案例,要求:分别输出实体model1.model2.model3的id和name值,这三个实体有相同的属性名字id和name. 1 public cla ...

  4. 跟着小老弟来学习Kotlin中的逆变和协变

    /   今日科技快讯   / 近日,小米创始人.董事长兼CEO雷军在抖音上开启了其直播带货的首秀.从晚上8点开播,到晚上10点,销售额就已经破亿.包括1000台售价49999元的透明电视在内的商品一推 ...

  5. java中的逆变、协变、不变概念讲解转载自http://www.cnblogs.com/en-heng/p/5041124.html,感谢编程路上的前辈们!

    En-Heng 无他,但手熟尔 博客园 首页 新随笔 联系 订阅 管理 随笔 - 32  文章 - 0  评论 - 33 Java中的逆变与协变 看下面一段代码 Number num = new In ...

  6. c 语言中双向链表逆转编程题,C/C++ 双链表之逆序的实例详解

    C/C++ 双链表之逆序的实例详解 一.结点结构 双向链表的数据结构定义如下: typedef struct node { ElemType data; struct node *prior stru ...

  7. 我的世界怎么修改服务器刷怪率,《我的世界》地图刷怪率变高方法详解

    <我的世界>地图刷怪率变高方法详解 2014-04-14 17:08:01来源:互联网编辑:评论(0) <我的世界>地图刷怪率变高方法详解​ 首先来看看地图的刷怪原理: 怪物能 ...

  8. android融合存储,科技瞭望台:8G真能变12G?详解手机内存融合技术

    科技瞭望台:8G真能变12G?详解手机内存融合技术 2021年04月27日 12:55作者:陈沐梁编辑:陈沐梁文章出处:泡泡网原创 分享 近段时间,内存融合或者说内存拓展技术,经常在不少手机厂商的宣传 ...

  9. 泛型委托的逆变和协变

    泛型委托:委托实际只是提供了4个方法的一个类定义.这4个方法包括:一个构造器.一个Invoke方法.一个BeginInvoke方法和一个EndInvoke方法.如果定义的一个委托类型指定了类型参数,编 ...

最新文章

  1. SAP S4HANA 实战LTMC - 打响了第一炮
  2. Windows安装python3.6.x版本
  3. 冒泡排序html代码,冒泡排序.html
  4. 团队开发项目--校园知网 nabcd 需求分析
  5. Hadoop分布式文件系统--HDFS结构分析
  6. xdebug影响php运行速度
  7. android旋转动画开源库,android 围绕中心旋转动画
  8. python中对文件的操作_Python对文件操作知识
  9. 基于Vue、vue-i18n实现国际化(多语言)
  10. FPGA实验记录一:1位全加器设计
  11. IP地址详解,网络分段
  12. 手把手教你使用LabVIEW OpenCV dnn实现物体识别(Object Detection)含源码
  13. Android之视频裁剪
  14. 微信公众号 接口配置
  15. 9.6.5对象的常引用
  16. 计算机原理处理器,多处理器结构-微计算机原理-电子发烧友网站
  17. 从头开始学习->JVM(九):垃圾收集(上)
  18. c语言中void和define,C语言里面的内联函数(inline)与宏定义(#define)探讨
  19. word中鼠标没有反应,可以动但是点哪都没反应
  20. 貌似在ubuntu下架了个web服务器,上传上次的flex调色板

热门文章

  1. Spring Boot----SpringBoot整合 Dubbo 和 Zookeeper
  2. cf1108E2 线段树类似扫描线
  3. [ubuntu setting]Change system language
  4. CI Weekly #17 | flow.ci 支持 Java 构建以及 Docker/DevOps 实践分享
  5. LFS,编译自己的Linux系统 - 前言
  6. VS2008 JS脚本调试总是调试旧代码 真不知道怎么回事?谁能帮帮我呀!
  7. 自动化测试框架:没有Surprise的原因
  8. 【发现问题】Java中PrintStream和PrintWriter的区别
  9. 计算机二级1605错误,word 出现windows installer 1605错误
  10. matlab晶闸管整流电路,采用Matlab/Simulink对三相桥式全控整流电路的仿真分析