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 ...
随机推荐
- C++外观模式和组合模式
外观模式应该是用的很多的一种模式,特别是当一个系统很复杂时,系统提供给客户的是一个简单的对外接口,而把里面复杂的结构都封装了起来.客户只需使用这些简单接口就能使用这个系统,而不需要关注内部复杂的结构. ...
- Qt ------ QTableView QTableWidget
QTableView model提供数据 view提供视图 view用来显示model的数据 必须将model绑定到某个view中才能显示 QStandardItemModel* mod ...
- 使用Sql语句快速将数据表转换成实体类
开发过程中经常需要根据数据表编写对应的实体类,下面是使用sql语句快速将数据表转换成对应实体类的代码,使用时只需要将第一行'TableName'引号里面的字母换成具体的表名称就行了: declare ...
- bzoj千题计划290:bzoj3143: [Hnoi2013]游走
http://www.lydsy.com/JudgeOnline/problem.php?id=3143 计算每条边经过的概率e[] 然后经过概率多的分配的编号大,经过概率少的分配的编号小 如何计算边 ...
- Zephir入门教程一
一.如何安装 zephir-安装和初体验:http://blog.csdn.net/u011142688/article/details/51619811 二.如何使用 需要切到工作目录下,也就是co ...
- Java SSM框架之MyBatis3(五)MyBatis之ResultMap详解
resultMap是Mybatis最强大的元素,它可以将查询到的复杂数据(比如查询到几个表中数据)映射到一个结果集当中. resultMap包含的元素: <!--column不做限制,可以为任意 ...
- ZYNQ. LwIP.PHY.KSZ9031RNX
新一块板子 米尔科技的 z-turn 使用的PHY芯片是Micrel的 KSZ9031RNX 而不是zedboard上的Marvell的. 直接使用lwip的echo server demo时会报错 ...
- CSS border系列
本文更新版链接 一.border 关于border的3个属性,分别为border-width.border-style.border-color. 其中,border-color默认为元素内容的前景色 ...
- python自动安装mysql5.7【转】
#!/usr/bin/env python import os import sys import re base_dir = '/opt/software/mysql-5.7.17-linux-gl ...
- mycat学习笔记
MyCAT简易入门_数据库技术_Linux公社-Linux系统门户网站http://www.linuxidc.com/Linux/2016-01/127382.htm mycat读写分离配置 - PE ...