C#中的协变(Covariance)和逆变(Contravariance)
摘要
● 协变和逆变的定义是什么?给我们带来了什么便利?如何应用?
● 对于可变的泛型接口,为什么要区分成协变的和逆变的两种?只要一种不是更方便吗?
● 为什么还有不可变的泛型接口,为什么有的泛型接口要故意声明成不可变的?
● 复合的可变泛型接口遵循哪些规则?
● 协变和逆变的数学定义是什么?如何利用数学模型解释C#4里的协变和逆变的规则?
前言
协变和逆变是c#4.0引入的新概念,主要是针对于泛型而言的。有了它们,我们可以更准确的定义泛型委托和接口。
首先举个栗子,比如IEnumerable<T> 接口是协变的,我们实现了一个这样的函数:
- static void PrintPersonName(IEnumerable<Person> persons)
- {
- foreach (Person person in persons)
- {
- Console.WriteLine(person.Name);
- }
- }
那么PrintPersonName这个方法就可以接受任何Person类的子类型列表作为它的参数。比如,若Student是Person的子类,那么可以这样调用:
- IList<Student> students = new List<Student>();
- PrintPersonName(students);
在C#4.0之前,上面的语句是无法通过编译的,因为IEnumerable接口是不可变(invariant)的。PrintPersonName方法只能接受Person列表作为其参数。如果Person的子类想实现同样的功能就必须自己PrintName方法,或者将PrintPersonName方法定义为泛型方法:
- static void PrintPersonName<T>(IEnumerable<T> persons) where T : Person
- {
- foreach (Person person in persons)
- {
- Console.WriteLine(person.Name);
- }
- }
上述方法可以运行的很好,但是不如直接协变接口这样简单明了。
协变和逆变的定义
1、不可变
如果一个接口的泛型参数没有in或out修饰符,它就是不可变的。比如IList<T>。我们既不能这样:
- IList<Person> personList1 = null;
- IList<Student> stuList = null;
- personList1 = stuList; // 编译错误:无法将IList<Student>隐式转换为IList<Person>
也不能这样:
- IList<Person> personList1 = null;
- IList<Student> stuList = null;
- stuList = personList1; // 编译错误:无法将IList<Person>隐式转换为IList<Student>
只能这样:
- IList<Person> personList1 = null;
- IList<Person> personList2 = null;
- personList1 = personList2;
2、协变
如果一个接口的泛型参数有out修饰符,它就是协变的。比如IEnumerable<out T>。我们既可以这样:
- IEnumerable<Person> persons1 = null;
- IEnumerable<Person> persons2 = null;
- persons1 = persons2;
也可以这样:
- IEnumerable<Person> persons = null;
- IEnumerable<Student> students = null;
- persons = students; // 可以将IEnumerable<Student>隐式转换为IEnumerable<Person>
但不能这样:
- IEnumerable<Person> persons = null;
- IEnumerable<Student> students = null;
- students = persons; // 无法将IList<Person>隐式转换为IList<Student>
3、逆变
如果一个接口的泛型参数有in修饰符,它就是逆变的。比如IComparer<in T>。我们既可以这样:
- IComparer<Person> personComparer1 = null;
- IComparer<Person> personComparer2 = null;
- personComparer1 = personComparer2;
也可以这样:
- IComparer<Person> personComparer = null;
- IComparer<Student> studentComparer = null;
- studentComparer = personComparer; // 可以把IComparer<Person>隐式转换为IComparer<Student>
但不能这样:
- IComparer<Person> personComparer = null;
- IComparer<Student> studentComparer = null;
- personComparer = studentComparer; // 无法将IComparer<Student>隐式转换为IComparer<Person>
4、小结
- 协变和逆变是一对互斥的概念
- 只有接口和委托的泛型参数可以是协变或逆变的
- 协变的泛型参数只能作为方法的返回值的类型
- 逆变的泛型参数只能作为方法的参数的类型
C#中协变和逆变的设计
在C#4.0的基础类库中,一些接口的泛型参数分别用了in或out修饰,比如:
- public interface IEnumerator<out T> : IDisposable, IEnumerator
- {
- T Current { get; }
- }
- public interface IComparable<in T>
- {
- int CompareTo(T other);
- }
而另一些却没有:
- public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
- {
- T this[int index] { get; set; }
- void Insert(int index, T item);
- void RemoveAt(int index);
- }
- public interface IEquatable<T>
- {
- bool Equals(T other);
- }
那么问题来了:
1、为什么 IComparable<in T> 被声明成逆变的而 IEquatable<T> 却被声明成不可变的?
2、为什么 IList<T> 被声明为不可变的?
简单来说,既然协变的接口的泛型参数只能作为函数的返回值,而逆变的接口的泛型参数只能作为函数的参数,那么像 IList<T> 这种 T 既要做为返回值又要作为参数的情况,自然只能声明为不可变的了。
3、为什么一个泛型参数不可以即是协变的又是逆变的?
简单来说是为了在编译期进行类型安全检查。
本文参考:http://www.cnblogs.com/1-2-3/archive/2010/09/27/covariance-contravariance-csharp4.html
C#中的协变(Covariance)和逆变(Contravariance)的更多相关文章
- C#中的协变OUT和逆变
泛型接口和泛型委托中经常使用可变性 in 逆变,out 协变 从 list<string>转到list<object> 称为协变 (string 从object 派生,那么 ...
- C# 逆变(Contravariance)/协变(Covariance) - 个人的理解
逆变(Contravariance)/协变(Covariance) 1. 基本概念 官方: 协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始 ...
- 协变(covariant)和逆变(contravariant)
我们知道子类转换到父类,在C#中是能够隐式转换的.这种子类到父类的转换就是协变. 而另外一种类似于父类转向子类的变换,可以简单的理解为“逆变”. 上面对逆变的简单理解有些牵强,因为协变和逆变只能针对接 ...
- C# 协变out 、逆变 in
需求:泛型使用多态性 备注:协变逆变只能修饰 接口和委托 简单理解: 1.使用 in 修饰后为逆变,只能用作形参使用 ,参考 public delegate void Action<in T&g ...
- Java中的协变与逆变
Java作为面向对象的典型语言,相比于C++而言,对类的继承和派生有着更简洁的设计(比如单根继承). 在继承派生的过程中,是符合Liskov替换原则(LSP)的.LSP总结起来,就一句话: 所有引用基 ...
- [改善Java代码]警惕泛型是不能协变和逆变的
什么叫做协变(covariance)和逆变(contravariance)? 在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是 ...
- C#-弄懂泛型和协变、逆变
脑图概览 泛型声明和使用 协变和逆变 <C#权威指南>上在委托篇中这样定义: 协变:委托方法的返回值类型直接或者间接地继承自委托前面的返回值类型; 逆变:委托签名中的参数类型继承自委托方法 ...
- Java中的逆变与协变
看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...
- Java中的逆变与协变(转)
看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...
随机推荐
- Java课程设计---团队博客
课设题目:购物车系统 题目要求: 1.先建立一个文本文件,定义出自己想要的商品.//也可用数据库以商品编号:商品名称:商品品牌:价格作为文件的内容,中间要求用分号或者分割 2.编写程序,定义一个商品类 ...
- 201521123051《Java程序设计》第十一周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. Java多线程同步的方法: (1)同步方法:即有synchronized关键字修饰的方法. 由于java的每个对象 ...
- 201521123066 《Java程序设计》第十周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 有关异常的知识点: 一段代码可能生成多种类型的异常,子类异常必须放在父类异常前面,否则会出现编译错误: 可以 ...
- Thinkphp3.2结合phpqrcode生成二维码(含Logo的二维码),附案例
首先,下载phpqrcode,将其解压到项目ThinkPHP\Library\Vendor目录下.Index_index.html(模板可自行配置) <form action="{:U ...
- 聊一聊我的阿里云ECS云主机
javaweb学习有段时间了,期间也编写了一些自己的小webapp应用,但是都是发布在我们自己的个人pc上的. 于是我在想:怎么样让自己的项目可以发到公网上面去,让朋友们能够来访问? 我首先想到的是: ...
- Mysql免安装版配置【图文版和文字版】
图文版 配置环境变量 新建一个my.ini文件,添加下面内容 [mysqld] basedir=C:\\software\Mysql\mysql-5.7.14-winx64 datadir=C:\\s ...
- bat文件逐行读取txt
From_Ip='192.138.60.16'@echo offfor /f "tokens=1,2 delims='" %%a in (D:\ETL\bat\config.txt ...
- idea启动tomcat报错:Error during artifact deployment. See server log for details.
出现这种情况的原因老夫猜想是改变了artifact然而tomcat的配置中的artifact没有重新配就会出现这种报错 打开tomcat配置 删除原来的artifact 新添加artifact 保存 ...
- css预处理器less和scss之sass介绍(二)
本来打算整理jQuery Mobile来着,但是没有研究明白,所以接着上个周的继续介绍... [scss中的基础语法] 1.scss中的变量 ①声明变量:$变量名:变量值 $width:100px ...
- GCD之死锁
GCD相当好用,但用不好就会死锁,始终要记着这样一句秘籍: 不要在串行队列放dispatch_sync.dispatch_apply 下面看几个例子 1 2 3 4 5 6 7 8 9 10 11 1 ...