为何引入协变、逆变

我们知道一个子类对象可以赋值给一个基类对象

Animal animal = new Animal();
Animal cat = new Cat();

那如果是用在泛型里面能行嘛?

List<Animal> animalsList = new List<Animal>();//pass
List<Animal> CatsList = new List<Cat>();//error

一组猫难道不是一组动物吗?错,是因为这里List<Animal> 与List<Cat>分别是不同的类(用ILSpy.exe 即可以看到),这两个类没有继承关系。

当然此处可以采用List<Animal> CatsList = new List<Cat>().Select(c=>(Animal)c).ToList();来实现。

泛型中的类型对应的都是强类型,泛型在使用的时候,存在不和谐的地方,正是由于这个原因,出现了协变和逆变。

协变的使用

IEnumerable<Animal> catList = new List<Cat>();//协变

此处将一组Cat赋值给一组动物,符合我们正常的理解,是协调的变化。

具体看IEnumerable接口,其公开了一个T类型返回值的接口方法,通过一个Out关键字指定该类型支持协变。具体如下:

    //
// 摘要:
// 公开枚举数,该枚举数支持在指定类型的集合上进行简单迭代。
//
// 类型参数:
// T:
// 要枚举的对象的类型。
[TypeDependencyAttribute("System.SZArrayHelper")]
public interface IEnumerable<out T> : IEnumerable
{
//
// 摘要:
// 返回一个循环访问集合的枚举器。
//
// 返回结果:
// 用于循环访问集合的枚举数。
IEnumerator<T> GetEnumerator();
}

这里IEnumerable是只读的,List是可以改的。如果List<string>可以变成List<object>的话,

即List<string>=List<object>;

那我往=List<object>里面add一个int怎么办?这样就类型不安全了,所以此处IEnumerable只能是支持协变的。

逆变的使用

协变逆变的定义

定义一个泛型接口

interface IFoo<T>

{

    void Method1(T param);

    T Method2();

}

如果我们允许协变,从IFoo<TSub>到IFoo<TParent>转换,那么IFoo.Method1(TSub)就会变成IFoo.Method1(TParent)。

我们都知道TParent是不能安全转换成TSub的,所以Method1这个方法就会变得不安全。

同样,如果我们允许反变IFoo<TParent>到IFoo<TSub>,则TParent IFoo.Method2()方法就会变成TSub IFoo.Method2(),

原本返回的TParent引用未必能够转换成TSub的引用,Method2的调用将是不安全的[返回值变成了具体的子类,父类的引用没法复制转换为子类引用了,违反LSP]。

故通过加入Out,In关键字来保证在泛型接口和泛型委托中,这种安全的类型转换。

总结

C#高级编程之泛型三(协变与逆变)的更多相关文章

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

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

  2. 转载.NET 4.0中的泛型的协变和逆变

    先做点准备工作,定义两个类:Animal类和其子类Dog类,一个泛型接口IMyInterface<T>, 他们的定义如下:   public class Animal { } public ...

  3. Kotlin泛型与协变及逆变原理剖析

    在上一次https://www.cnblogs.com/webor2006/p/11234941.html中学习了数据类[data class]相关的知识,这次会学习关于泛型相关的东东,其中有关于泛型 ...

  4. Java用通配符 获得泛型的协变和逆变

    Java对应泛型的协变和逆变

  5. Java泛型的协变与逆变

    泛型擦除 Java的泛型本质上不是真正的泛型,而是利用了类型擦除(type erasure),比如下面的代码就会出现错误: 报的错误是:both methods  have same erasure ...

  6. C#-弄懂泛型和协变、逆变

    脑图概览 泛型声明和使用 协变和逆变 <C#权威指南>上在委托篇中这样定义: 协变:委托方法的返回值类型直接或者间接地继承自委托前面的返回值类型; 逆变:委托签名中的参数类型继承自委托方法 ...

  7. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  8. C# 泛型的协变和逆变

    1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...

  9. C# 泛型的协变和逆变 (转载)

    1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量. 协变和逆变是两个相互对立的概念: ...

随机推荐

  1. centos8平台:举例讲解redis6的ACL功能(redis6.0.1)

    一,为什么redis6要增加acl功能模块? 什么是acl? 访问控制列表(ACL)是一种基于包过滤的访问控制技术, 它可以根据设定的条件对接口上的数据包进行过滤,允许其通过或丢弃 redis6增加了 ...

  2. scrapy-下载器中间件 随机切换user_agent

    from faker import Faker class MySpiderMiddleware(object): def __init__(self): self.fake = Faker() de ...

  3. 树莓派调试PCF8591遇到的小问题

    错误提示:bus = smbus.SMBus(1) IOError: [Errno 2] No such file or directory 提示的内容为端口没有打开即IIC端口:如图,打开IIC使能 ...

  4. 前端基础——HTML(一)

    HTML html超文本标记语言 前端三层 HTML结构层 css样式层 JavaScript行为层 其他多媒体内容(图片,音频等等) 互联网运行过程 客 --http请求--> 服 户 htt ...

  5. Linux入门到放弃之二《目录处理常用命令的使用方法》

    一,目录操作命令 1.用pwd命令查看当前所在的目录: 2.用ls命令列出此目录下的文件和目录: 3.列出此目录下包括隐藏文件在内的所有文件和目录并且长格式显示: (  -l表示长格式,-a表示隐藏文 ...

  6. java 第一课 笔记

    java是一种解释型语言 Java提供了内存自动管理:不涉及指针:单继承. classpath:字节码文件的路径,执行java.exe时,会查找并解释*.class文件 set classpath=. ...

  7. 图的全部实现(邻接矩阵 邻接表 BFS DFS 最小生成树 最短路径等)

    1 /** 2 * C: Dijkstra算法获取最短路径(邻接矩阵) 3 * 6 */ 7 8 #include <stdio.h> 9 #include <stdlib.h> ...

  8. python的deque(双向)队列详解

    首先 python的队列有很多种 Python标准库中包含了四种队列,分别是queue.Queue / asyncio.Queue / multiprocessing.Queue / collecti ...

  9. JDK源码阅读-------自学笔记(二十五)(java.util.Vector 自定义讲解)

    Vector 向量 Vector简述 1).Vector底层是用数组实现的List 2).虽然线程安全,但是效率低,所以并不是安全就是好的 3).底层大量方法添加synchronized同步标记,sy ...

  10. webpack学习遇到大坑(纯属自己记录)

    分清webpack1与webpack2区别 1.webpack2的loader不能使用简写了,否则会报如下的错 正确如下: 2.node-sass安装失败,无法下载:Cannot download h ...