了解C#的协变和逆变
前言
在引用类型系统时,协变、逆变和不变性具有如下定义。 这些示例假定一个名为 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)
内置的泛型逆变委托Action
、Func
、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
参考
- 泛型中的协变和逆变 | Microsoft Docs
- 《你必须知道的.NET(第2版)》
了解C#的协变和逆变的更多相关文章
- C#4.0泛型的协变,逆变深入剖析
C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...
- C#协变和逆变
我们知道在C#中,是可以将派生类的实例赋值给基类对象的.
- C# 泛型的协变和逆变
1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...
- 不变(Invariant), 协变(Covarinat), 逆变(Contravariant) : 一个程序猿进化的故事
阿袁工作的第1天: 不变(Invariant), 协变(Covarinat), 逆变(Contravariant)的初次约 阿袁,早!开始工作吧. 阿袁在笔记上写下今天工作清单: 实现一个scala类 ...
- 再谈对协变和逆变的理解(Updated)
去年写过一篇博客谈了下我自己对协变和逆变的理解,现在回头看发现当时还是太过“肤浅”,根本没理解.不久前还写过一篇“黑”Java泛型的博客,猛一回头又是“肤浅”,今天学习Java泛型的时候又看到了协变和 ...
- 【转】c# 协变和逆变
本文转自:http://www.cnblogs.com/rr163/p/4047404.html C#的协变和逆变 由子类向父类方向转变是协变,用out关键字标识,由父类向子类方向转变是逆变,用in关 ...
- .NET 4.0中的泛型的协变和逆变
转自:http://www.cnblogs.com/jingzhongliumei/archive/2012/07/02/2573149.html 先做点准备工作,定义两个类:Animal类和其子类D ...
- 深入理解 C# 协变和逆变
msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型. “逆变”则是指能够使用派生程度更小的类型. 解释的很正确,大致就是这样,不过不够直白. 直白的理解: “协变” ...
- Java用通配符 获得泛型的协变和逆变
Java对应泛型的协变和逆变
- [改善Java代码]警惕泛型是不能协变和逆变的
什么叫做协变(covariance)和逆变(contravariance)? 在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是 ...
随机推荐
- SpringBoot(1):初始SpringBoot
一. SpringBoot 简介 1. SpringBoot介绍 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程.该框架使用了特 ...
- Google Guava 常用集合方法
/** * Author: momo * Date: 2018/6/7 * Description: */ public class ListTest { public static void mai ...
- 严重危害警告!Log4j 执行漏洞被公开!
12 月 10 日凌晨,Apache 开源项目 Log4j2 的远程代码执行漏洞细节被公开,漏洞威胁等级为:严重. Log4j2 是一个基于 Java 的日志记录工具.它重写了 Log4j 框架,引入 ...
- pthread_cond_signal与pthread_cond_wait详解
转:http://blog.chinaunix.net/uid-11572501-id-3456343.html //pthread_cond_signal 只发信号,内部不会解锁,在Linux 线程 ...
- 动手做个 AI 机器人,帮我回消息!
大家好,我是鱼皮,自从做了知识分享,我的微信就没消停过,平均每天会收到几百个消息,大部分都是学编程的朋友向我咨询编程问题. 但毕竟我只有一个人,没法所有消息都一个个回复,所以也是很愧疚和无力吧:另外我 ...
- 当在myeclipse里发送邮件有错误时,不妨把环境都改成jdk的
当在myeclipse里发送邮件有错误时,不妨把环境都改成jdk的, 如果是jre的环境会出错,改成jdk的就行了.
- ACwing2.01背包问题
题目: 有 N 件物品和一个容量是 V 的背包.每件物品只能使用一次. 第 i 件物品的体积是 vi,价值是 wi. 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大. 输出最 ...
- JAVA使用经纬度通过腾讯地图API获取详细地址
官方文档:https://lbs.qq.com/service/webService/webServiceGuide/webServiceGcoder package com.weixin.map; ...
- 给初学者的STM32(Cortex-M3)中断原理及编程方法介绍 [原创www.cnblogs.com/helesheng]
本人编著的<基于STM32的嵌入式系统原理及应用>(ISBN:9787030697974)刚刚在科学出版社出版.这本书花费了半年以上的时间,凝聚了笔者作为高校教师和嵌入式工程师的一些经验, ...
- Protobuf 动态加载 .proto 文件并操作 Message
Google Protocol Buffer 的常规用法需要使用 protoc 将 .proto 编译成 .pb.h 和 .pb.cc,这样做效率非常高,但是耦合性也很高.在某些追求通用性而不追求性能 ...