C#泛型的抗变与协变
C#泛型的抗变与协变
学习自
C#本质论6.0
https://www.cnblogs.com/pugang/archive/2011/11/09/2242380.html
Overview
一直以来,被抗变与协变的定义搞得头昏脑涨掰持不清,如果在加上泛型在其中作祟,就更加两眼发懵了。所以就暂时略过了这一拦路虎,但是今天在学习Kotlin泛型的时候再一次碰到了这里拦路虎,只硬着头皮迎难而上了。
抗边与协变的定义
协变: 子类想父类方向的类型转换称之为协变
//string[] 向 object[] 协变转换
string [] strArray = new string[10];
object [] objArray = strArray;
抗变: 父类向子类方向的类型转换称之为抗变
//委托的抗变
Action<object> actObj = Set;
Action<string> actStr = actObj;
抗变和协变是针对于泛型接口和委托来说的,但是数组也是存在协变的,但是数组的协变是不安全的
泛型的抗变与协变
受限制的泛型协变
在这里有一个让初学泛型的人非常疑惑的一个问题:
** 为什么List 的集合不能复制为List 的集合 **
虽然string可以向object协变转化, 但是因为类型会不安全,这种操作是没法的。
唯恐说不明白,举个例子吧
咱们 假定,下面的代码不会报错,且看我一步步分析
List<string> strList = new List<string>();
//在这里会报错,但是咱们先假设他们可以顺利执行
List<object> objList = strList;
//因为objList实际其中装着的是strList,那么可以肯定将string类型的数据添加到集合中是完全没有问题的
objList.Add("Hello");
//因为objList实际其中装着的是strList,因为string类型和int类型肯定是会有冲突的,假设这样的代码可以执行那么只有崩溃一条路可以走
objList.Add(1);
//这就是为什么,List<string> 不能赋值给List<int>
out 修饰符允许协变性
之所以会出现上面的问题,是因为List 不仅仅可以读取,还可以被写入,这样就造成了类型不安全。所以上述操作是不允许的。因为问题出在了写入上面,所以如果我们提供一个只读的操作是否就可以避免这种错误呢?
看下列的代码
public interface IReadOnlyPair<T>
{
T First { get; }
T Second { get; }
}
public interface IPair<T>
{
T First { get; set; }
T Second { get; set; }
}
public class Pair<T> : IPair<T>, IReadOnlyPair<T>
{
public Pair(T first, T second)
{
}
public T First
{
get
{
throw new System.NotImplementedException();
}
set
{
throw new System.NotImplementedException();
}
}
public T Second
{
get
{
throw new System.NotImplementedException();
}
set
{
throw new System.NotImplementedException();
}
}
}
static void Main(string[] args)
{
Pair<string> strPair = new Pair<string>("First", "Second");
//虽然代码看上去十分合理,但是在这里仍然会报错,因为类型仍然存在不安全
IReadOnlyPair<object> objPair = strPair;
Console.WriteLine(objPair.First);
Console.WriteLine(objPair.Second);
}
IReaderOnlyPair是只读的,所以它根本就没有写入操作,那么这样就避免了上面所提到的因为添加操作而出现的类型安全问题,所以会编译应该不会有问题,那么咱们现在的这个是否能够通过呢?
答案是否定的,因为类型仍然不安全,为了结果这种问题C#4.0提供了out修饰符来允许我们进行类型安全的协变
为什么是不安全的看下面代码就知道了
public interface IReadOnlyPair<T>
{
T First { get; }
T Second { get; }
//如果接口中,又多出了下面的方法,那么泛型参数T就又成了了“输入”类型的泛型参数,在一次出现了类型不安全问题
void Set(T t);
}
为了支持协变,我们需要对代码进行一下小小的修改
public interface IReadOnlyPair<out T>
{
T First { get; }
T Second { get; }
}
out 修饰符的作用是,告诉编译器,这个泛型参数只用于 输出, 即只用 返回值和属性, 经过这些变化之后,上面的代码就不会报错了。
但是协变转换,仍然是有一些限制的
- 只有泛型委托和泛型接口是可以进行协变转换的,而泛型类是不可以的
- 参与协变的泛型类型必须是引用类型,而不能是像
int这样的值类型 - 接口或者委托声明为支持协变了的话,那么泛型参数只能用作
属性和返回值而不能用于方法的参数
泛型的抗变
抗变相对于协变是相对比较难一点的,但是只要抓住了其中的关键原因,理解起来也非常容易。
直接上例子比讲定义更清晰
static void Main(string[] args)
{
Action<object> actObj = Set;
//在这里你可能会感到奇怪,Object类型怎么可能可以直接地转换为string类型呢
Action<string> actStr = actObj;
actStr.Invoke("dfdf");
}
static void Set(object obj)
{
Console.WriteLine(obj.ToString());
}
在泛型接口和委托的协变中 遵循了这么一个原则 子类可以安全地调用父类的任何成员 ,上述代码中 actObj 委托指向了Set方法,接下来将actObj赋值给了 Action actStr, 那么现在actStr中存放的实际上是actObj对象指向的方法是 Set(object ojb) 方法,注意string类型可以安全地调用Object类中的所有的成员,所以actStr.Invoke("dfdf"); 这句代码完全没有问题。
in类型修饰符,允许抗变
Action委托是支持抗变的,如果想让自己定义的泛型具有抗变的特性,需要在泛型类型前加上 in 关键字才行。
public delegate void Action<in T>(T obj);
抗变也有着一些规则限制
- 只能是引用类型
- 只能作为方法参数,不能用作返回值和属性
- 只有泛型委托和泛型接口是可以进行抗变转换,而泛型类是不可以的
结语
本文记录了我对泛型的抗变和协变的理解,以供大家参考,如果文中有什么谬误或不足之处欢迎指出。
C#泛型的抗变与协变的更多相关文章
- c# 泛型的抗变和协变
namespace test { // 泛型的协变,T 只能作为返回的参数 public interface Class1<out T> { T Get(); int Count { ge ...
- C#泛型中的抗变和协变
在.net4之前,泛型接口是不变的..net4通过协变和抗变为泛型接口和泛型委托添加了一个重要的拓展 1.抗变:如果泛型类型用out关键字标注,泛型接口就是协变的.这也意味着返回类型只能是T. 实例: ...
- 《C#高级编程》学习笔记------抗变和协变
1.协变和抗变 在.NET 4之前,泛型接口是不变的..NET 4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展.协变和抗变指对参数和返回值的类型进行转换.例如,可以给一个需要Shape参数的 ...
- .NET 4.0中的泛型逆变和协变
转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...
- C# 抗变与协变的理解
我们知道 方法的参数是协变的: void display(shape o) 如果类Rectangle 继承于shape类,那我们可以给该方法传入Rectangle类的实例. 而方法的返回类型是抗变的, ...
- C#核心语法讲解-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)
泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...
- C#核心语法-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)
泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...
- 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4
前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...
- .NET 4.0中的泛型的协变和逆变
转自:http://www.cnblogs.com/jingzhongliumei/archive/2012/07/02/2573149.html 先做点准备工作,定义两个类:Animal类和其子类D ...
随机推荐
- 借读:分布式锁和双写Redis
本帖最后由 howtodown 于 2016-10-3 16:01 编辑问题导读1.为什么会产生分布式锁?2.使用分布式锁的方法有哪些?3.本文创造的分布式锁的双写Redis框架都包含哪些内容? ...
- Python 堆与堆排序
堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满足 ...
- Sublime Text 2 绿化与汉化 [Windows篇]
其实 ST3 已经出了很久了,可是我这个人恋旧,一直钟爱 ST2,所以就选择她了.最近我的 ST2 越来越卡,甚至有时候输入都会延迟1秒,所以打算自己搞个绿化版. 打开 Sublime Text 官网 ...
- JS模块化写法(转)
一.原始写法 模块就是实现特定功能的一组方法. 只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块. function m1(){ //... } function m2(){ // ...
- 第13月第13天 iOS 放大消失动画
1. - (void) animate { [UIView animateWithDuration:0.9 animations:^{ CGAffineTransform transform = CG ...
- Ubuntu GNOME单击任务栏图标最小化设置
在Ubuntu GNOME的发行版中,桌面使用的是GNOME,GNOME可以像Windows那样有一个底部任务栏,在Ubuntu GNOME中它称为 dash to dock,如下图: Windows ...
- 在 Linux 上找出并解决程序错误的主要方法【转】
转自:https://www.ibm.com/developerworks/cn/linux/sdk/l-debug/index.html 本文讨论了四种调试 Linux 程序的情况.在第 1 种情况 ...
- 标准linu休眠和唤醒机制分析(四)【转】
转自:http://blog.csdn.net/lizhiguo0532/article/details/6453552 suspend第三.四.五阶段:platform.processor.core ...
- 使用pt-table-checksum校验MySQL主从复制【转】
pt-table-checksum是一个基于MySQL数据库主从架构在线数据一致性校验工具.其工作原理在主库上运行, 通过对同步的表在主从段执行checksum, 从而判断数据是否一致.在校验完毕时, ...
- 创蓝语音服务(语音通知验证码).net
public static string PostUrl = "http://zapi.253.com/msg/HttpBatchSendSM"; static void Main ...