为何引入协变、逆变

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

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. monolog handler用哪个

    Handlers 记录日志到文件与系统日志(syslog) StreamHandler:记录日志到任何 PHP stream,用它来记录到文件. RotatingFileHandler: 每天一个文件 ...

  2. spring boot:使mybatis访问多个druid数据源(spring boot 2.3.2)

    一,为什么要使用多个数据源? 1,什么情况下需要使用多个数据源? 当我们需要访问不同的数据库时,则需要配置配置多个数据源, 例如:电商的业务数据库(包括用户/商品/订单等)            和统 ...

  3. spring boot: 用redis的消息订阅功能更新应用内的caffeine本地缓存(spring boot 2.3.2)

    一,为什么要更新caffeine缓存? 1,caffeine缓存的优点和缺点 生产环境中,caffeine缓存是我们在应用中使用的本地缓存, 它的优势在于存在于应用内,访问速度最快,通常都不到1ms就 ...

  4. centos8平台:redis6配置启用io多线程(redis6.0.1)

    一,linux平台上redis6的安装 请参见这一篇: https://www.cnblogs.com/architectforest/p/12830056.html 说明:刘宏缔的架构森林是一个专注 ...

  5. Linux安装软件时90%的人会遇到这个报错,如何解决?

    提示 Could not get lock /var/lib/dpkg/lock 报错? 有些小伙伴在使用 apt 包管理器更新或安装软件时,可能会遇到过诸如以下的错误提示: E: Could not ...

  6. Lambda表达式(一)

    Lambda表达式其实就是实现SAM接口的语法糖,作用就是简化代码的冗余,同时可读性也好过匿名内部类. 以下先一步步演示是如何把大段的代码变成一句代码的,加强理解! 第一种正常的写法 1 public ...

  7. linux安装日志切割程序

    ====linux安装日志切割程序==== 安装 gcc(1) yum insatll gcc (2)# cd cronolog-1.6.2 4.运行安装 # ./configure# make# m ...

  8. 【转】Extension Libraries and Loading Other Image Formats

    FROM: http://lazyfoo.net/tutorials/SDL/06_extension_libraries_and_loading_other_image_formats/index. ...

  9. Ubuntu Kylin 部署 .Net Core 应用程序

    前几日在头条上看到了 优麒麟(https://www.ubuntukylin.com/) ,出于好奇,就下载安装玩玩,整体感觉不错.当然这不是重点,重点是要在它上面部署的.Net Core 应用程序. ...

  10. 自动化测试之Selenium篇(一):环境搭建

    当前无论找工作或者是实际项目应用,自动化测试扮演着非常重要的角色,今天我们来学习下Selenium的环境搭建 Selenium简述 Selenium是一个强大的开源Web功能测试工具系列 可进行读入测 ...