http://www.cnblogs.com/qixuejia/p/4383068.html

深入理解 C# 协变和逆变

 

msdn 解释如下:

“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。

“逆变”则是指能够使用派生程度更小的类型。

解释的很正确,大致就是这样,不过不够直白。

直白的理解:

“协变”->”和谐的变”->”很自然的变化”->string->object :协变。 

“逆变”->”逆常的变”->”不正常的变化”->object->string 逆变。 

上面是个人对协变和逆变的理解,比起记住那些派生,类型,原始指定,更大,更小之类的词语,个人认为要容易点。

下面是一则笑话:

一个星期的每一天应该这样念:

星期一 = 忙day; 
星期二 = 求死day; 
星期三 = 未死day; 
星期四 = 受死day; 
星期五 = 福来day; 
星期六 = 洒脱day; 
星期天 = 伤day

为了演示协变和逆变,以及之间的区别,请创建控制台程序CAStudy,手动添加两个类:

因为是演示,所以都是个空类,

只是有一点记住Dog 继承自Animal,

所以Dog变成Animal 就是和谐的变化(协变),而如果Animal 变成Dog就是不正常的变化(逆变)

在Main函数中输入:

因为Dog继承自Animal,所以Animal aAnimal = aDog; aDog 会隐式的转变为Animal.

但是List<Dog> 不继承List<Animal> 所以出现下面的提示:

如果想要转换的话,应该使用下面的代码:

List<Animal> lstAnimal2 = lstDogs.Select(d => (Animal)d).ToList();

可以看到一个lstDogs 变成lstAnimal 是多么复杂的操作了。

正因如此,所以微软新增了两个关键字:Out,In,下面是他们的msdn解释:

协变的英文是:“covariant”,逆变的英文是:“Contravariant”

为什么Microsoft选择的是”Out” 和”In” 作为特性而不是它们呢?

我个人的理解:

因为协变和逆变的英文太复杂了,并没有体现协变和逆变的不同,但是out 和 in 却很直白。

out: 输出(作为结果),in:输入(作为参数)

所以如果有一个泛型参数标记为out,则代表它是用来输出的,只能作为结果返回,而如果有一个泛型参数标记为in,则代表它是用来输入的,也就是它只能作为参数。

目前out 和in 关键字只能在接口和委托中使用,微软使用out 和 in 标记的接口和委托大致如下:

先看下第一个IEnumerable<T>

和刚开始说的一样,T 用out 标记,所以T代表了输出,也就是只能作为结果返回。

public static void Main()

{

Dog aDog = new Dog();

Animal aAnimal = aDog;

List<Dog> lstDogs = new List<Dog>();

//List<Animal> lstAnimal = lstDogs;

List<Animal> lstAnimal2 = lstDogs.Select(d => (Animal)d).ToList();

IEnumerable<Dog> someDogs = new List<Dog>();

IEnumerable<Animal> someAnimals = someDogs;

}

因为T只能做结果返回,所以T不会被修改,编译器就可以推断下面的语句强制转换合法,所以

IEnumerable<Animal> someAnimals = someDogs;

可以通过编译器的检查,反编译代码如下:

虽然通过了C#编译器的检查,但是il 并不知道协变和逆变,还是得乖乖的强制转换。

在这里我看到了这句话:

IEnumerable<Animal> enumerable2 = (IEnumerable<Animal>) enumerable1;

那么是不是可以List<Animal> lstAnimal3 = (List<Animal>)lstDogs; 呢?

想要回答这个问题需要在回头看看Clr via C# 关于泛型和接口的章节了,我就不解释了,

答案是不可以。

上面演示的是协变,接下来要演示下逆变。

为了演示逆变,那么就要找个in标记的接口或者委托了,最简单的就是:

在Main函数中添加:

Action<Animal> actionAnimal = new Action<Animal>(a => {/*让动物叫*/ });

Action<Dog> actionDog = actionAnimal;

actionDog(aDog);

很明显actionAnimal 是让动物叫,因为Dog是Animal,那么既然Animal 都能叫,Dog肯定也能叫。

In 关键字:逆变,代表输入,代表着只能被使用,不能作为返回值,所以C#编译器可以根据in关键字推断这个泛型类型只能被使用,所以Action<Dog> actionDog = actionAnimal;可以通过编译器的检查。

再次演示Out关键字:

添加两个类:

public interface IMyList<out T>

{

T GetElement();

}

public class MyList<T> : IMyList<T>

{

public T GetElement()

{

return default(T);

}

}

因为out 关键字,所以下面的代码可以通过编译

IMyList<Dog> myDogs = new MyList<Dog>();

IMyList<Animal> myAnimals = myDogs;

将上面的两个类修改为:

public interface IMyList<out T>

{

T GetElement();

void ChangeT(T t);

}

public class MyList<T> : IMyList<T>

{

public T GetElement()

{

return default(T);

}

public void ChangeT(T t)

{

//Change T

}

}

编译:

因为T被out修饰,所以T只能作为参数。

同样修改两个类如下:

public interface IMyList<in T>

{

T GetElement();

void ChangeT(T t);

}

public class MyList<T> : IMyList<T>

{

public T GetElement()

{

return default(T);

}

public void ChangeT(T t)

{

//Change T

}

}

这一次使用in关键字。

编译:

因为用in关键字标记,所以T只能被使用,不能作为返回值。

最后修改代码为:

public interface IMyList<in T>

{

void ChangeT(T t);

}

public class MyList<T> : IMyList<T>

{

public void ChangeT(T t)

{

//Change T

}

}

编译成功,因为in代表了逆变,所以

IMyList<Animal> myAnimals = new MyList<Animal>();

IMyList<Dog> myDogs = myAnimals;

可以编译成功!。

【转】深入理解 C# 协变和逆变的更多相关文章

  1. 深入理解 C# 协变和逆变 (转载)

      深入理解 C# 协变和逆变 msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型. “逆变”则是指能够使用派生程度更小的类型. 解释的很正确,大致就是这样,不过不 ...

  2. 深入理解 C# 协变和逆变

    msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型. “逆变”则是指能够使用派生程度更小的类型. 解释的很正确,大致就是这样,不过不够直白. 直白的理解: “协变” ...

  3. C#-弄懂泛型和协变、逆变

    脑图概览 泛型声明和使用 协变和逆变 <C#权威指南>上在委托篇中这样定义: 协变:委托方法的返回值类型直接或者间接地继承自委托前面的返回值类型; 逆变:委托签名中的参数类型继承自委托方法 ...

  4. 再谈对协变和逆变的理解(Updated)

    去年写过一篇博客谈了下我自己对协变和逆变的理解,现在回头看发现当时还是太过“肤浅”,根本没理解.不久前还写过一篇“黑”Java泛型的博客,猛一回头又是“肤浅”,今天学习Java泛型的时候又看到了协变和 ...

  5. c# 协变和逆变的理解

    1. 是什么 1.1 协变 协变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型.如 string 到 object 的转换.多见于类型参数用作方法的返回值. 1.2 逆变 逆变指能够 ...

  6. Scala教程之:深入理解协变和逆变

    文章目录 函数的参数和返回值 可变类型的变异 在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型:使用-表示逆变类型:非转化类型不需要添加标记. 假如我们定义一个cla ...

  7. C# 泛型的协变和逆变

    1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...

  8. 不变(Invariant), 协变(Covarinat), 逆变(Contravariant) : 一个程序猿进化的故事

    阿袁工作的第1天: 不变(Invariant), 协变(Covarinat), 逆变(Contravariant)的初次约 阿袁,早!开始工作吧. 阿袁在笔记上写下今天工作清单: 实现一个scala类 ...

  9. c#的协变和逆变

    关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承,所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是D ...

随机推荐

  1. iOS解析新浪微博的@##以及URL链接并展示

    最近在做一个跟微博相关的应用.其中涉及到了对微博中@.##以及URL链接的解析与展示.分享一下个人处理的方式,希望对需要的人有所帮助. 最终的展现效果: 首先,第一步是你得从纯文本中找到它们.毫无疑问 ...

  2. IOS 偏好设置数据 存 取(Preferences文件夹)

    很多iOS应用都支持偏好设置,比如保存用户名.密码.字体大小等设 置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能 每个应用都有个NSUserDefaults实例,通过它来存取偏好设置 比如 ...

  3. 【洛谷4424】[HNOI/AHOI2018] 寻宝游戏(位运算思维题)

    点此看题面 大致题意: 给你\(n\)个\(m\)位二进制数.每组询问给你一个\(m\)位二进制数,要求你从\(0\)开始,依次对于这\(n\)个数进行\(and\)或\(or\)操作,问有多少种方案 ...

  4. Python实现屏蔽敏感词

    一.需求 1. 有一个文件,里面有一些敏感词汇,用户输入一段话,若包含这些词,就用**代替,并输出 二.实现代码 f = open('lib.txt', 'r') result = '' f1 = i ...

  5. 网络编程——网络模型和InetAddress类

    第1章 网络通信协议 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样.在计算机网络中,这些连接和 ...

  6. ASP.NET补充

    字典类的子集 using System.Collections.Generic; Dictionary<string, string> dicB = new Dictionary<s ...

  7. AFN post的数据编码格式问题

    想到写任何关于AFN的东西其实我是拒绝的,因为自己这也是第一次用,毕竟AFN现在是最为流行的网络框架了,害怕自己理解的有误,所以不敢造次! 先在这里大致讲解一下过程吧,后期发现了再更正(主要是想让看官 ...

  8. 深入浅出:了解JavaScript的六种继承

    了解继承前我们需要了解函数的构造,方便我们理解. 常见六种继承方式: 1.原型继承call和apply: 2.原型拷贝:循环父函数protype的key值=子函数prototype的key值: 3.原 ...

  9. vue学习之路 - 2.基本操作(上)

    基本操作(上) 本章节简介: vue的安装 vue实例创建 数据绑定渲染 表单数据双向绑定 事件处理 安装 安装方式有三种: 一.vue官网直接下载 http://vuejs.org/js/vue.m ...

  10. nodejs源码—初始化

    概述 相信很多的人,每天在终端不止一遍的执行着node这条命令,对于很多人来说,它就像一个黑盒,并不知道背后到底发生了什么,本文将会为大家揭开这个神秘的面纱,由于本人水平有限,所以只是讲一个大概其,主 ...