深入理解 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# 协变和逆变的更多相关文章
- 【转】深入理解 C# 协变和逆变
http://www.cnblogs.com/qixuejia/p/4383068.html 深入理解 C# 协变和逆变 msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程 ...
- 深入理解 C# 协变和逆变 (转载)
深入理解 C# 协变和逆变 msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型. “逆变”则是指能够使用派生程度更小的类型. 解释的很正确,大致就是这样,不过不 ...
- C#-弄懂泛型和协变、逆变
脑图概览 泛型声明和使用 协变和逆变 <C#权威指南>上在委托篇中这样定义: 协变:委托方法的返回值类型直接或者间接地继承自委托前面的返回值类型; 逆变:委托签名中的参数类型继承自委托方法 ...
- 再谈对协变和逆变的理解(Updated)
去年写过一篇博客谈了下我自己对协变和逆变的理解,现在回头看发现当时还是太过“肤浅”,根本没理解.不久前还写过一篇“黑”Java泛型的博客,猛一回头又是“肤浅”,今天学习Java泛型的时候又看到了协变和 ...
- c# 协变和逆变的理解
1. 是什么 1.1 协变 协变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型.如 string 到 object 的转换.多见于类型参数用作方法的返回值. 1.2 逆变 逆变指能够 ...
- Scala教程之:深入理解协变和逆变
文章目录 函数的参数和返回值 可变类型的变异 在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型:使用-表示逆变类型:非转化类型不需要添加标记. 假如我们定义一个cla ...
- C# 泛型的协变和逆变
1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...
- 不变(Invariant), 协变(Covarinat), 逆变(Contravariant) : 一个程序猿进化的故事
阿袁工作的第1天: 不变(Invariant), 协变(Covarinat), 逆变(Contravariant)的初次约 阿袁,早!开始工作吧. 阿袁在笔记上写下今天工作清单: 实现一个scala类 ...
- c#的协变和逆变
关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承,所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是D ...
随机推荐
- [翻译] 使用ASP.NET MVC操作过滤器记录日志
[翻译] 使用ASP.NET MVC操作过滤器记录日志 原文地址:http://www.singingeels.com/Articles/Logging_with_ASPNET_MVC_Action_ ...
- 分析Linux内核创建一个新进程的过程【转】
转自:http://www.cnblogs.com/MarkWoo/p/4420588.html 前言说明 本篇为网易云课堂Linux内核分析课程的第六周作业,本次作业我们将具体来分析fork系统调用 ...
- Linux驱动学习笔记(6)信号量(semaphore)与互斥量(mutex)【转】
转自:http://blog.chinaunix.net/uid-24943863-id-3193530.html 并发导致竟态,从而导致对共享数据的非控制访问,产生非预期结果,我们要避免竟态的发生. ...
- Maven之Nexus构建企业级Maven仓库
什么是Nexus? Nexus是Maven仓库管理器,用来搭建一个本地仓库服务器,这样做的好处是便于管理,节省网络资源,速度快,还有一个非常有用的功能就是可以通过项目的SNAPSHOT版本管理,来进行 ...
- linux的lsof命令
删除文件,用命令df -h看磁盘空间,但发现没有变化. 这种情况,需要使用命令查看是不是文件被某个进程占用了: /usr/sbin/lsof |grep deleted
- PHP获取不了React Native Fecth参数的解决办法是怎么样呢?
fetch('https://mywebsite.com/endpoint/', { method: 'POST',headers: {'Accept': 'application/json','Co ...
- Objective-C占位符
%@ 对象 %d, %i 整数 %li 长整型 %u 无符号整形 %f 浮点/双字 %x, %X 二进制整数 %o 八进制整数 %zu size_t %p 指针 %e 浮点/ 双字 (科学计算) (d ...
- 20145227 《Java程序设计》第7周学习总结
20145227 <Java程序设计>第7周学习总结 教材学习内容总结 第十二章 Lambda 如果使用JDK8的话,可以使用Lambda特性去除重复的信息. 在只有Lambda表达式的情 ...
- python: shutil模块 -拷贝文件
import shutil #拷贝文件 #存在文档1文件 shutil.copyfile('文档1','新文件') 随机验证码-4位 import random random_code='' for ...
- json-lib date对象转json ,加入自定义日期格式处理
import net.sf.json.JSONObject; import net.sf.json.JsonConfig; import net.sf.json.processors.JsonValu ...