摘要

● 协变和逆变的定义是什么?给我们带来了什么便利?如何应用?
  ● 对于可变的泛型接口,为什么要区分成协变的和逆变的两种?只要一种不是更方便吗?
  ● 为什么还有不可变的泛型接口,为什么有的泛型接口要故意声明成不可变的?
  ● 复合的可变泛型接口遵循哪些规则?
  ● 协变和逆变的数学定义是什么?如何利用数学模型解释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、不可变

如果一个接口的泛型参数没有inout修饰符,它就是不可变的。比如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)的更多相关文章

  1. C#中的协变OUT和逆变

    泛型接口和泛型委托中经常使用可变性 in  逆变,out  协变 从 list<string>转到list<object> 称为协变 (string 从object 派生,那么 ...

  2. C# 逆变(Contravariance)/协变(Covariance) - 个人的理解

    逆变(Contravariance)/协变(Covariance) 1. 基本概念 官方: 协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始 ...

  3. 协变(covariant)和逆变(contravariant)

    我们知道子类转换到父类,在C#中是能够隐式转换的.这种子类到父类的转换就是协变. 而另外一种类似于父类转向子类的变换,可以简单的理解为“逆变”. 上面对逆变的简单理解有些牵强,因为协变和逆变只能针对接 ...

  4. C# 协变out 、逆变 in

    需求:泛型使用多态性 备注:协变逆变只能修饰 接口和委托 简单理解: 1.使用 in 修饰后为逆变,只能用作形参使用 ,参考 public delegate void Action<in T&g ...

  5. Java中的协变与逆变

    Java作为面向对象的典型语言,相比于C++而言,对类的继承和派生有着更简洁的设计(比如单根继承). 在继承派生的过程中,是符合Liskov替换原则(LSP)的.LSP总结起来,就一句话: 所有引用基 ...

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

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

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

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

  8. Java中的逆变与协变

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

  9. Java中的逆变与协变(转)

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

随机推荐

  1. 201521123022 《Java程序设计》 第十一周学习总结

    1.本章学习总结 2.书面作业 本次PTA作业题集多线程 Q1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问 Q1.1 除了使用synchronized修饰方法实现互斥同步访问,还 ...

  2. 关于APP在小米5s第一次安装启动后,点击home返回桌面,再次进入重进闪屏页问题

    现象 今天工作中,在对公司产品进行测试的时候,程序员小哥点出了一个问题.问题点出的步骤是这样的: 1.安装APP 2.点击打开 3.经过闪屏页,进入主页后,点击HOME键 4.再次进入程序会重新进入闪 ...

  3. Oracle与Mysql区别简述

    在Mysql中,一个用户下可以创建多个库: 而在Oracle中,Oracle服务器是由两部分组成 数据库实例[理解为对象,看不见的] 数据库[理解为类,看得见的] 一个数据库实例可拥有多个用户,一个用 ...

  4. Spring-java-模板设计模式

    1,模板设计模式指的是将相应的模板方法提取出来在专门的位置定义,然后把相同调用过程操作,通过模板来实现对于模板设计模式而言,一般有两种实现方式 1)基于继承的实现 2)基于组合的实现 Spring的J ...

  5. Ubuntu Server 17.04安装GNOME指令

    短短几行指令,从安装到打开: apt-get install gnome-shell apt-get install xinit xshell 开启  关闭: Ctrl+Alt+F1   Ctrl+A ...

  6. Java中的类型转换(Integer、Long、String)

    这段时间将项目中一个模块参照C++源代码,实现一个JAVA版.主要功能是将一些字段信息转换为String类型,传输后可以进行解析. Integer.Long转为String,Java本身提供了这种转换 ...

  7. 《MATLAB从入门到放弃》打通 “矩阵” 障碍

    目录: »   矩阵的生成与大小  >   简单矩阵的生成  >  随机矩阵的生成  >   矩阵的大小 »  矩阵的索引与访问 »  矩阵的拼接与裁剪 >  矩阵的拼接 &g ...

  8. LuoguP1196_银河英雄传说_KEY

    银河英雄传说 题目描述 公元5801年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展. 宇宙历799年,银河系的两大军事集团在巴米利恩星域爆 ...

  9. eclipse中搜狗输入法中文状态下输出的全是英文

    在eclipse中搜狗输入法变成了如图这样 在中文状态下,提示的全是中文. 查询到的解决方案: 快捷键ctrl+shift+E关闭搜狗智能英文.然而与eclipse中 Ctrl+shift+E 快捷键 ...

  10. TCP/IP(二)物理层详解

    前言 在前面说了一下,计算机网络的大概内容,没有去深刻的去了解它,这篇文章给大家分享一下物理层! 我们知道ISO模型是七层,TCP/IP模型是五层,而tcp/ip协议只将七层概括为4层,我们将学习其中 ...