【转】逆变与协变详解

逆变(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("");

它实际的流程是这样的:

image

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

1

2

3

4

Base<object> BaseObject = null;

Base<string> BaseString = null;

BaseObject = BaseString;//编译失败

BaseObject.Test("");

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

image

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

image

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

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

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

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

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可以通过,那么它的“协变流程图”应该如下:

image

图跟前面那些大致一样,但理解它要跟问题相反(上面问题是先定义好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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/436980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式(二)设计模式的本质

简介 设计模式是计算机前辈们&#xff0c;总结项目开发成败经验&#xff0c;得出的一套最佳实践理论。它并不是高高在上、不切实际的理论&#xff0c;而是具体到代码编写层面的指导理论。 从学习编写代码开始&#xff0c;我们就被教导&#xff0c;要写高内聚、低耦合、可复用…

angular设置referer_Angular-cli 构建应用的一些配置

Angular-cli 构建应用的一些配置标签(空格分隔)&#xff1a; Angular直接使用 ng build --prod --build-optimizer --base-href/ 来发布base-href可以设置服务器上的某个子路径&#xff0c;使用 ng build --base-href/my/path/如果打包静态文件(js和css)不放在和index.html同一…

设计模式(三)创建型模式

前言 根据菜鸟教程的目录&#xff0c;我们首先来看看创建型模式。 创建型模式研究&#xff1a; 实际应用中通常有哪些不同的创建对象的场景&#xff1b;在不同的场景下&#xff0c;如何更好地编写创建对象的代码。主要研究构造函数。 下面分别对创建型模式下的各种具体模式进…

CSDN改版,找不到各种入口,链接放下面

https://mp.csdn.net/console/article?spm1010.2135.3001.5128 https://mp.csdn.net/console/column/allColumnList 分类管理 https://mp.csdn.net/console/article 文章管理 https://mp.csdn.net/console/upDetailed 资源管理 https://mp.csdn.net/editor/html?spm1011.…

python中pca算法_python实现PCA算法01

python实现PCA算法Software version&#xff1a; Python 2.7.12 |Anaconda 4.2.0 (64-bit)|法1. 编程一步一步实现法2. sklearn我们以定义函数的形式来一步一步进行1.1 导入模块&#xff1a;Numpy&#xff0c;Pandas# -*- coding: utf-8 -*-# Time : 2017/8/17 14:20# Author :…

设计模式(一)预备知识

前言 学习设计模式需要有面向对象编程的基础&#xff0c;要基本掌握一种面向对象的编程语言。关于面向对象编程思想&#xff0c;我们有另外一篇文章专门讲解&#xff1a;面向过程和面向对象的联系和区别。 知识点 虚函数 在类的成员函数定义中&#xff0c;形式上为定义为vir…

【转】GitHub 从单机到联机:玩转 Pull Request

最近在参与一个叫 Exercism 的项目&#xff0c;这是一个由 GitHub 生态工程师 Katrina Owen 发起的编程练习社区&#xff0c;提供了超过50门语言的练习。作为用户&#xff0c;你仅需使用命令行工具即可下载和提交练习&#xff0c;提交后还可以和社区中其他学习者交流讨论。 Exe…

android cmake 打印_Android使用CMAKE编译libjpeg

本文主要介绍使用 CMAKE 编译 libjpeg-turbo 类库&#xff0c;本文相关代码请在GitHub-TurboJpegSample 查看。libjpeg-turbo 附GitHub 地址&#xff0c;libjpeg-turbo 是个运用极其广泛的库&#xff0c;可以说&#xff0c;基本上电脑上手机上能见到的 JPEG 压缩的地方用的一般…

设计模式(四)结构型模式

前言 结构型设计模式&#xff0c;主要研究&#xff1a; 主要有哪些场景使用结构型设计模式&#xff1b;每种场景应该使用何种设计模式&#xff1b;以程序中的功能为核心&#xff0c;研究程序功能的组织结构。所以这一章&#xff0c;我们要把“功能结构”作为研究的核心。 下…

centos 删除crontab_centos下crontab的使用

4.cron文件语法:分 小时 日 月 星期 命令0-59 0-23 1-31 1-12 0-6 command (取值范围,0表示周日一般一行对应一个任务)5.记住几个特殊符号的含义:“*”代表取值范围内的数字,“/”代表”每”,“-”代表从某个数字到某个数字,“,”分开…

【转】GitHub客户端操作1--仓库相关操作github团队协作流程

1、创建仓库 点击“”&#xff0c;点击Create,然后填写Name为my&#xff0c;点击Create repository&#xff0c;即可创建一个my仓库 2、从本地仓库存放位置&#xff0c;添加test仓库到GitHub客户端 点击“”&#xff0c;点击Add,然后点击Browse&#xff0c;选择仓库存放位置&a…

音视频相关链接

开发库 FFmpeg FFmpeg官网 FFmpeg github 平台相关 DirectShow DirectShow简介 DirectShow官网

【转】GitHub客户端操作2--分支操作

简单分支操作 &#xff08;1&#xff09;创建新分支&#xff1a;my分支 备注&#xff1a;新创建的分支&#xff1a;my分支里面的内容是和master分支里面的内容是完全一样的。 &#xff08;2&#xff09;删除my分支 &#xff08;3&#xff09;修改my分支【在my分支上进行项目内…

uniapp动态修改样式_uniapp样式动态绑定

场景一&#xff1a;用户点击按钮后动态切换按钮选中样式(如图)已上线未开始//选择状态selectState(e){this.whichSelectede.currentTarget.dataset.state}.state-btn-content{//write your style.state-btn-selected{ ... }.state-btn-noselect{ ... }}注&#xff1a;需要注意的…

音视频工程师(初步)(一)音视频的基本概念

1. 前言 本文是音视频工程师系列的第一篇文章。 学习音视频的过程中发现&#xff0c;网络上高质量的音视频技术博客不是很多。具有代表性的是 雷神的 雷霄骅的CSDN博客 因为笔者是一名普通的软件开发人员&#xff0c;一向不喜欢高高在上或者晦涩难懂的理论&#xff0c;我们…

【转】GitHub客户端操作3--pull Request(拉请求)

一、参考说明 参考文章一&#xff1a;Github上提交代码(pullrequest) 网址&#xff1a; https://jingyan.baidu.com/article/358570f64dcdc2ce4724fc32.html 参考文章二&#xff1a;GitHub——Pull Request 网址&#xff1a;http://blog.csdn.net/u012325167/article/detai…

webstorm 内存溢出怎么弄_webstrom 内存溢出,软件崩溃卡死解决的方法

今天用gulp搭建了一个工程,准备做一个体育h5的项目,其中需要用到sass代码压缩,加版本号等功能.gulpfile.js和package.json都是已经写好的.我用CMD命令窗口cnpm安装node_modules依赖文件.当我安装完毕之后用webstrom打开时,第一件事先把node_modules排除忽略了.如下:然后我去编辑…

躺平也要看,2022年计算机相关考试汇总

1. 全国计算机专业资格考试&#xff08;软考&#xff09; 1.1 官网 全国官网&#xff1a;https://www.ruankao.org.cn/&#xff0c;从全国官网首页的底部可以直达各个省区的考试机构报名网站。 1.2 考试安排 软考一年两次。 场次报名时间考试时间上半年 预计报名时间在2月…

【转】GitHub上README.md教程

本文是转载文章&#xff0c;文章的来源&#xff1a;csdn博客 博主&#xff1a;果冻虾仁 文章&#xff1a; GitHub上README写法暨GFM语法解读 博文地址&#xff1a;https://blog.csdn.net/guodongxiaren/article/details/23690801 转载请保留原作者guodongxiaren的原文地址&…

python supervisor flask_supervisor配合uwsgi部署flask应用

这已经是第N次来部署flask应用了, 但是每次都花了不少时间在配置上面, 这里一次性记录下, 备个忘~写在前面其实使用uwsgi来部署flask应用在官网上已经有较为详细的 文档 了, 推荐先读一下.先解决一些问题:为何使用 uwsgi 来部署应用? 方便管理; 较为常见的部署方式; 可配置性较…