.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(); 那如果是用在泛型里面能行嘛? ...
随机推荐
- postgresql逻辑结构--索引(六)
一.索引简介 二.索引分类 三.创建索引 四.修改索引 五.删除索引
- elasticSearch6源码分析(6)http和transport模块
1.http模块概述 The http module allows to expose Elasticsearch APIs over HTTP. The http mechanism is comp ...
- input输入框file类型第二次不触发onchange事件的解决办法,简单有效
在网上看了很多办法,现在将网上大部分说法总结如下: 网上说法: 原因:选择一次后onchange事件没有绑定到input标签上: 解决办法:拷贝一份input标签的副本,每次选择后对原input ...
- 深入出不来nodejs源码-timer模块(C++篇)
终于可以填上坑了. 简单回顾一下之前JS篇内容,每一次setTimeout的调用,会在一个对象中添加一个键值对,键为延迟时间,值为一个链表,将所有该时间对应的事件串起来,图如下: 而每一个延迟键值对的 ...
- windows7 资源管理器无法显示常用
1.windows7 资源管理器无法显示常用 2.windows7 资源管理器无法附加的解决办法. 删除以下文件夹内的所有文件即可%APPDATA%\Microsoft\Windows\Recent\ ...
- Lightning Chart 8.4版新功能
LightningChat V8.4版在如下功能上做了改进 边距 缩放功能 Legend box 的自动定位 正投影 相机orientation与rotation系统 全新3D 坐标空间转换器 Poi ...
- JavaScript之String总汇
一.常用属性 ·length:返回字符串中字符长度 let str = 'asd '; str.length = 1;//无法手动修改,只读 console.log(str.length);//4 二 ...
- Asp.Net WebForm生命周期的详解
一.Asp.Net页面生命周期的概念 当我们在浏览器地址栏中输入网址,回车查看页面时,这时会向服务器端IIS)发送一个request请求,服务器就会判断发送过来的请求页面,当完全识别 TTP页面处理程 ...
- [日常] Go语言圣经-匿名函数习题
Go语言圣经-匿名函数1.拥有函数名的函数只能在包级语法块中被声明,通过函数字面量(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值2.通过这种方式定义的函数可以访 ...
- Mybatis一对一,一对多
一对一 假如有student表(学生表)和student_card表(学生证表). student表中有一个字段self_card用来查student_card,student_card表中有一个st ...