前言

在引用类型系统时,协变、逆变和不变性具有如下定义。 这些示例假定一个名为 Base 的基类和一个名为 Derived的派生类。

  • Covariance

使你能够使用比原始指定的类型派生程度更大的类型。

你可以将 IEnumerable 的实例分配给 IEnumerable 类型的变量。

  • Contravariance

使你能够使用比原始指定的类型更泛型(派生程度更小)的类型。

你可以将 Action 的实例分配给 Action 类型的变量。

  • Invariance

表示只能使用最初指定的类型。 固定泛型类型参数既不是协变,也不是逆变。

你无法将 List 的实例分配给 List 类型的变量,反之亦然。

以上来自于官方文档对协变、逆变、不变性的解释

为啥C#需要协变和逆变?

我们首先来看一段代码:


class FooBase{ } class Foo : FooBase
{ } var foo = new Foo();
FooBase fooBase = foo; //以下代码在.NET 4.0之前是不被支持的
IEnumerable<Foo> foo = new List<Foo>();
IEnumerable<FooBase> fooBase = foo;

因此,在这里实际上可以回答,C#的协变和逆变就是主要有两种目的:

  • 兼容性:.NET2.0就推出了泛型,而从.NET 2.0到.NET 3.5期间不支持对泛型接口中的占位符T支持隐式转换,因此在.NET4.0推出协变和逆变
  • 为了支持更广泛的隐式类型的转换,在这里就是在泛型体系中支持

在C#中,目前只有泛型接口和泛型委托可以支持协变和逆变,

协变(Covariance)

内置的泛型协变接口,IEnumerator<T>IQuerable<T>IGrouping<Tkey, TElement>:


public interface IEnumerable<out T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
} public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable
{ } public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
{
TKey Key { get; }
}

因此这段代码在.NET4.0及以上版本将不会编译报错:

IEnumerable<Foo> foo = new List<Foo>();
IEnumerable<FooBase> fooBase = foo;

实际上,对于协变,有下面的约束,否则则会在编译时报错:

  • 泛型参数占位符以out关键子标识,并且占位符T只能用于只读属性、方法或者委托的返回值,out简而易懂,就是输出的意思
  • 当要进行类型转换,占位符T要转换的目标类型也必须是其基类,上述例子则是Foo隐式转为FooBase

逆变(Contravariance)

内置的泛型逆变委托ActionFunc Predicate,内置的泛型逆变接口IComparable<T>IEquatable<T>:


public delegate void Action<in T>(T obj); public delegate TResult Func<in T, out TResult>(T arg); public delegate bool Predicate<in T>(T obj); public interface IComparable<in T>
{
int CompareTo(T? other);
} public interface IEquatable<T>
{
bool Equals(T? other);
}

而逆变的用法则是这样:

Action<FooBase> fooBaseAction = new Action<FooBase>((a)=>Console.WriteLine(a));

Action<Foo> fooAction = fooBaseAction;

而对于逆变,则跟协变相反,有下面的约束,否则也是编译时报错:

  • 要想标识为逆变,应该是要在占位符T前标识in,只能用于只写属性、方法或者委托的输入参数
  • 当要进行类型转换,占位符T要转换的目标类型也必须是其子类,上述例子则是FooBase转为Foo

总结

  • 协变和逆变只对泛型委托和泛型接口有效,对普通的泛型类和泛型方法无效
  • 协变和逆变的类型必须是引用类型,因为值类型不具备继承性,因此类型转换存在不兼容性
  • 泛型接口和泛型委托可同时存在协变和逆变的类型参数,即占位符T

参考

了解C#的协变和逆变的更多相关文章

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

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

  2. C#协变和逆变

    我们知道在C#中,是可以将派生类的实例赋值给基类对象的.

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

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

  4. 不变(Invariant), 协变(Covarinat), 逆变(Contravariant) : 一个程序猿进化的故事

    阿袁工作的第1天: 不变(Invariant), 协变(Covarinat), 逆变(Contravariant)的初次约 阿袁,早!开始工作吧. 阿袁在笔记上写下今天工作清单: 实现一个scala类 ...

  5. 再谈对协变和逆变的理解(Updated)

    去年写过一篇博客谈了下我自己对协变和逆变的理解,现在回头看发现当时还是太过“肤浅”,根本没理解.不久前还写过一篇“黑”Java泛型的博客,猛一回头又是“肤浅”,今天学习Java泛型的时候又看到了协变和 ...

  6. 【转】c# 协变和逆变

    本文转自:http://www.cnblogs.com/rr163/p/4047404.html C#的协变和逆变 由子类向父类方向转变是协变,用out关键字标识,由父类向子类方向转变是逆变,用in关 ...

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

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

  8. 深入理解 C# 协变和逆变

    msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型. “逆变”则是指能够使用派生程度更小的类型. 解释的很正确,大致就是这样,不过不够直白. 直白的理解: “协变” ...

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

    Java对应泛型的协变和逆变

  10. [改善Java代码]警惕泛型是不能协变和逆变的

    什么叫做协变(covariance)和逆变(contravariance)? 在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是 ...

随机推荐

  1. Maven错误收集

    Eclipse 创建项目时报错 Could not resolve archetype org.apache.maven.archetypes:maven-archetype-quickstart:1 ...

  2. 通过js禁用浏览器的回退事件

    js代码: <script> history.pushState(null, null, document.URL); window.addEventListener('popstate' ...

  3. 深入浅出 Docker

    一.什么Docker 从作用的角度: Docker是一个为开发人员和系统管理员开发.迁移和运行应用程序的平台.应用程序通过Docker打包成Docker Image后,可以实现统一的方式来下载.启动. ...

  4. 沉淀vue相关知识(主要还是个人积累用)

    路由懒加载的配置: const Home= () =>import('../components/Home') //使用ES6中的路由懒加载的方式 const About= () =>im ...

  5. 企业级BI是自研还是采购?

    企业级BI是自研还是采购? 上一篇<企业级BI为什么这么难做?>,谈到了企业级BI项目所具有的特殊背景,以及在"破局"方面的一点思考,其中谈论的焦点主要是在IT开发项目 ...

  6. [BUUCTF]PWN——jarvisoj_test_your_memory

    jarvisoj_test_your_memory 附件 步骤: 例行检查,32位程序,开启了nx保护 试运行一下程序,看看大概的情况 32位ida打开,习惯性的检索程序里的字符串,看到了有关flag ...

  7. 为什么众多软件厂商无法提供APS高级计划排程系统?工厂目前生产计划是怎么排产的?

    一.行业现状如想了解一下目前现状,去考察一下上了ERP的企业,会发现一个有趣的现象该企业无论ERP软件搞得如何如火如荼,似乎都与生产调度人员无关. 车间里或者生产线上的生产作业计划.生产过程的调度和管 ...

  8. 突破结构限制的“数据透视表”(Excel技巧集团)

    出个题:根据A2:C16生成E2:G18的汇总结果.这里的汇总是求和. 遇到这种情况,首选肯定是函数公式,虽然数据源表是个很规范的一维表,可以用数据透视表,可是想建构到上图那么奇葩,数据透视表无此异能 ...

  9. jetbrain家的fleet(已获得预览权限)直接对标vscode , fleet有望超过vscode吗?今天我们实际操作下

    申请预览版 等待了一周终于得到了预览版的机会 今天就来简单使用下. 前言 工程管理大多使用的是maven , 在maven之前还有ant 这个应该已经没多少人在使用了,或者说新人基本不在使用ant , ...

  10. Docker从入门到精通(七)——容器数据共享

    什么是容器数据共享?简单来说就是容器与容器之间数据共享,容器与宿主机数据共享. 1.为什么需要数据共享? ①.数据持久化 比如我们有一个MySQL集群,通过容器启动,那么项目运行过程中的数据是保存在容 ...