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#泛型的抗变与协变的更多相关文章

  1. c# 泛型的抗变和协变

    namespace test { // 泛型的协变,T 只能作为返回的参数 public interface Class1<out T> { T Get(); int Count { ge ...

  2. C#泛型中的抗变和协变

    在.net4之前,泛型接口是不变的..net4通过协变和抗变为泛型接口和泛型委托添加了一个重要的拓展 1.抗变:如果泛型类型用out关键字标注,泛型接口就是协变的.这也意味着返回类型只能是T. 实例: ...

  3. 《C#高级编程》学习笔记------抗变和协变

    1.协变和抗变 在.NET 4之前,泛型接口是不变的..NET 4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展.协变和抗变指对参数和返回值的类型进行转换.例如,可以给一个需要Shape参数的 ...

  4. .NET 4.0中的泛型逆变和协变

    转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...

  5. C# 抗变与协变的理解

    我们知道 方法的参数是协变的: void display(shape o) 如果类Rectangle 继承于shape类,那我们可以给该方法传入Rectangle类的实例. 而方法的返回类型是抗变的, ...

  6. C#核心语法讲解-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)

    泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...

  7. C#核心语法-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)

    泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...

  8. 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4

    前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...

  9. .NET 4.0中的泛型的协变和逆变

    转自:http://www.cnblogs.com/jingzhongliumei/archive/2012/07/02/2573149.html 先做点准备工作,定义两个类:Animal类和其子类D ...

随机推荐

  1. 函数和常用模块【day05】:装饰器前奏(一)

    本节内容 定义 原则 实现装饰器的储备知识 函数及变量 高阶函数 一.定义 1.装饰器:本质是函数. 2.功能:用来装饰其他函数,顾名思义就是,为其他的函数添加附件功能的. 二.原则 不能修改被装饰函 ...

  2. sql查询结果存入DataTable,然后从DataTable取数据

    public static DataTable SqlConnectionInformation() { string connstr = ConfigurationManager.Connectio ...

  3. js监听浏览器tab窗口切换

    js监听浏览器tab窗口切换 ——IT唐伯虎 摘要:js监听浏览器tab窗口切换. if (document.hidden !== undefined) {  document.addEventLis ...

  4. 解决pdf打印预览中遇到特殊字符,导出失败问题

    本博客是自己在学习和工作途中的积累与总结,仅供自己参考,也欢迎大家转载,转载时请注明出处. 由于近日由于pdf中存在特殊字符导致导出失败,主要原因是"&"字符与freema ...

  5. 一个ssm综合小案例-商品订单管理-第二天

    准确来说是第二三天,一时兴起,把这个小项目一鼓作气写完了(较大的bug 均已被我手动捉出并 fix )才来写一篇博客. 接上文 第一天配置继续讲解:

  6. Docker CE的安装 与镜像加速

    Docker CE 的安装与镜像加速 Docker CE是docker的开源版本 CENTOS 安装Docker CE 系统要求: 操作系统需要使用centos7() centos-extras库 必 ...

  7. Asp.Net中索引器的用法

    索引器定义类似于属性,但其功能与属性并不相同.索引器提供一种特殊的方法编写get和set访问器.属性可以像访问字段一样访问对象的数据,索引器可以使用户像访问数组一样访问类成员. 一.索引器特性 1.g ...

  8. Angular 下的 function

    angular.lowercas 将指定的字符串转换为小写的 Usage(使用方法) angular.lowercase(string);   Arguments Param Type Details ...

  9. checkbox判断不为空

    checkbox不为空 html页面: @foreach($seals as $v) <input type="checkbox" name="seal_id[]& ...

  10. Fiddler抓取HTTPS最全(强)攻略

    本文来自于柠檬班49期学员superman童鞋的学习笔记.希望对同样是测试萌新的你有帮助,如果觉得好,可以点个赞噢~ 对于想抓取HTTPS的测试初学者来说,常用的工具就是fiddler.可是在初学时, ...