.NET泛型中的协变与逆变
泛型的可变性:协变性和逆变性
实质上,可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用。
我们已经习惯了普通继承中的可变性:例如,若某方法声明返回类型为Stream,在实现时可以返回一个MemoryStream。泛型可变性的概念与此相同,但要略微复杂一些。可变性应用于泛型接口和泛型委托的类型参数中,这一点必须引起注意。
可变性有两种类型:协变性和逆变性。二者概念基本相同,只是在上下文中转换的方向不同。
我们先从协变性开始,它通常要好理解一些。
协变性:从API返回的值
协变性用于向调用者返回某项操作的值。例如一个简单的表示工厂模式的泛型接口,它只包含一个方法CreateInstanse,返回适当类型的实例。代码如下:
/// <summary>
/// 使用out关键字表示协变
/// </summary>
/// <typeparam name="T"></typeparam>
interface IFactory<out T>
{
T CreateInstance();
}
现在,T在接口中只出现了一次(除了在签名中),它仅作为返回值使用,即方法的输出。这意味着可以将特定类型的工厂视为更一般类型的工厂。如在现实世界里,你可以将比萨工厂视为食品工厂。
逆变性:传入API的值
逆变性则相反。它指的是调用者向API传入值,即API是在消费值,而不是产生值。我们来想象另一个简单的接口——它可以向控制台打印特定的文档类型。同样,它也只有一个方法Print:
/// <summary>
/// 使用in关键字表示逆变
/// </summary>
/// <typeparam name="T"></typeparam>
interface IPrettyPrinter<in T>
{
void Print(T document);
}
这次T只作为参数出现在了接口的输入位置。具体而言,如果我们实现了IPrettyPrinter<SourceCode>,就可以将其当作IPrettyPrinter<CSharpCode>来使用。
不变性:双向传递的值
如果协变性适用于仅从API输出值的情况,而逆变性用于仅向API输入值的情况,那么如果值双向传递会如何呢?简而言之,什么也不会发生。这种类型是不变体(invariant)。下面的接口表示可以对数据类型进行序列化和反序列化的类型:
/// <summary>
/// 泛型类型的不变性,既不用 in 关键字限制,也不用 out 关键字限制
/// </summary>
/// <typeparam name="T"></typeparam>
interface IStorage<T>
{
byte[] Serialize(T value); T Deserialize(byte[] data);
}
这时,如果存在一个具有特定类型T的IStorage<T>实例,我们不能将其视为该接口更具体或更一般类型的实现。如果以协变的方式使用(如将IStorage<Customer>视为IStorage<Person>),则可能在调用Serialize时传入一个无法处理的对象。
类似地,如果以逆变的方式使用,则可能在反序列化数据时得到一个预料之外的类型。如果有助于理解的话,可以将不变性看成ref参数:按引用传递变量,其类型必须与参数本身的类型完全一致,因为值被传入了方法内部,并且同样被高效地传出。
更多
详见MSDN:https://docs.microsoft.com/zh-cn/dotnet/standard/generics/covariance-and-contravariance
.NET泛型中的协变与逆变的更多相关文章
- Java泛型中的协变和逆变
Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛 ...
- C#4.0新增功能03 泛型中的协变和逆变
连载目录 [已更新最新开发文章,点击查看详细] 协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体 ...
- Scala中的协变,逆变,上界,下界等
Scala中的协变,逆变,上界,下界等 目录 [−] Java中的协变和逆变 Scala的协变 Scala的逆变 下界lower bounds 上界upper bounds 综合协变,逆变,上界,下界 ...
- C#4.0中的协变和逆变
原文地址 谈谈.Net中的协变和逆变 关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Anima ...
- .net中的协变和逆变
百度:委托中的协变和逆变. 百度:.net中的协变和逆变. 协变是从子类转为父类. 逆变是从父类到子类. 这样理解不一定严谨或者正确.需要具体看代码研究.
- [改善Java代码]警惕泛型是不能协变和逆变的
什么叫做协变(covariance)和逆变(contravariance)? 在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是 ...
- Java语言中的协变和逆变(zz)
转载声明: 本文转载至:http://swiftlet.net/archives/1950 协变和逆变指的是宽类型和窄类型在某种情况下的替换或交换的特性.简单的说,协变就是用一个窄类型替代宽类型,而逆 ...
- Java中的协变与逆变
Java作为面向对象的典型语言,相比于C++而言,对类的继承和派生有着更简洁的设计(比如单根继承). 在继承派生的过程中,是符合Liskov替换原则(LSP)的.LSP总结起来,就一句话: 所有引用基 ...
- C#高级编程之泛型三(协变与逆变)
为何引入协变.逆变 我们知道一个子类对象可以赋值给一个基类对象 Animal animal = new Animal(); Animal cat = new Cat(); 那如果是用在泛型里面能行嘛? ...
随机推荐
- Intellij IDEA 编译等级与源代码等级不一致问题
错误:Error:java: javacTask: source release 1.7 requires target release 1.7 原因:生成class字节码的java版本,低于了源代码 ...
- Python -- 网络编程 -- 简单抓取网页
抓取网页: urllib.request.urlopen(url).read().decode('utf-8') --- (百度是utf-8,谷歌不是utf-8,也不是cp936,ascii也不行 ...
- vue 运行npm run dev报错
npm run dev运行时报错,原因有很多. 一般用下面这种方法都能解决的. 最简单粗暴的方法: 1.删除依赖包node_modules 2.然后重新npm install就行了 (如果这步报错了, ...
- 理解 async/await 的执行
这是一篇简单的短文章,方便理解. 开局先丢官宣:sec-async-function-definitions 这个链接是对 await 的解释,解释了它的执行. await 的执行意味着(官宣巴拉巴拉 ...
- [Python 从入门到放弃] 5. 文件与异常(一)
1.文件操作: 文件操作包含读/写 从文件中读取数据 向文件写入数据 Python中内置了open()方法用于文件操作 (更多关于open()BIF介绍 阅读此篇) 基本模板: 1.获取文件对象 2. ...
- Java中的构造器与垃圾回收
构造器 在我们初始化对象时,如果希望设置一些默认值,那么就可以使用构造器,在Java中,构造器使用和类同名的名字且没有返回值,如下 class Test{ private String name; T ...
- 记一次解决CSS定位bug思路
事因 网站中的遮罩层大都有一个问题,就是在这个遮罩层中滑动,里面的内容也会跟着滑动,我是这样想的,既然都有这个问题,干脆写一个通用的插件出来,省的每个还得单独处理.如果是单独处理这个问题是比较好解决的 ...
- 33-hadoop-cdh搭建coudemanager安装
http://www.aboutyun.com/thread-9219-1-1.html 非常强大的博客 ! ! ! 1, cdh简介 CDH (Cloudera’s Distribution, i ...
- C++ 各种继承
博客园首页博问闪存新随笔联系订阅 管理随笔- 文章- 评论- C++继承:公有,私有,保护 公有继承(public).私有继承(private).保护继承(protected)是常用的三种继承方式. ...
- C#基础知识回顾---你不知道的Lazy<T>
对象的创建方式,始终代表了软件工业的生产力方向,代表了先进软件技术发展的方向,也代表了广大程序开发者的集体智慧.以new的方式创建,通过工厂方法,利用IoC容器,都以不同的方式实现了活生生实例成员的创 ...