那些年搞不懂的"协变"和"逆变"

Posted 大壮他哥-专注于营销软件开发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了那些年搞不懂的"协变"和"逆变"相关的知识,希望对你有一定的参考价值。

  博主之前也不是很清楚协变与逆变,今天在书上看到了有关于协变还是逆变的介绍感觉还是不太懂,后来看了一篇园子里面一位朋友的文章,顿时茅塞顿开。本文里面会有自己的一些见解也会引用博友的一些正文,希望通过本篇,能让大家对协变与逆变不再陌生。

What\'s 协变逆变?

  从字面理解协变就是"妥协的变化",而逆变则是"逆天的变化",哈哈,并不标准,我们来看看MSDN的解释:  

    “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。

    “逆变”则是指能够使用派生程度更小的类型。

  解释的很正确,大致就是这样,不过不够直白。通俗来讲,协变就是从小到大,逆变则是从大到小。。协变逆变只会出现在泛型类和委托中,下面我们通过例子来了解协变和逆变。

  

  按照我们的理解,Dog继承自Animal,所以Animal aAnimal = aDog; aDog 编辑器会隐式的转变为Animal。但是List<Dog> 不继承List<Animal> ,它们的身份是相等的,所以转换失败。

  如果想要转换的话我们可以使用以下的代码:

  List<Animal> animals= dogs.Select(c=>(Animal)c).ToList();

  这种写法太麻烦了,有没有一种方式可以直接赋值呢?答案是可以的,微软为了帮助我们开发人员简化这种写法,帮我们定义了Out和in两个关键字,我们看下官方对这两个关键字的解释:

  

  

  Out关键字可以简单的定义为返回值参数,只能作为返回值。

  In关键字则定义为输入型参数,其含义指In对应的类型在泛型成员中不能作为返回值。

  和刚开始说的一样,T 用out 标记,所以T代表了输出,也就是只能作为结果返回。

  

  因为T只能做结果返回,所以T不会被修改,编译器就可以推断下面的语句强制转换合法,所以一下代码编译通过

    IEnumerable<Animal> animals = dogs;  

 

  再看逆变,在Main函数中添加:

  1. Action<AnimalactionAnimal = new Action<Animal>(a => {/*让动物叫*/ });  
  2. Action<DogactionDog = actionAnimal;  
  3. actionDog(aDog); 

  很明显actionAnimal 是让动物叫,因为Dog是Animal,那么既然Animal 都能叫,Dog肯定也能叫。

  In 关键字:逆变,代表输入,代表着只能被使用,不能作为返回值,所以C#编译器可以根据in关键字推断这个泛型类型只能被使用,所以Action<Dog> actionDog = actionAnimal;可以通过编译器的检查。

  再次演示Out关键字:添加两个类:

  

  out关键字在上图中只用于返回值,所以编辑器不会报错。我们再来修改一下类

  

  如果泛型参数标记为out,泛型类成员参数又定义了T类型的话则编译不通过。同样的方法我们来测试下逆变:

  

 

总结

  Out:代表协变,只能当返回值类型使用,不能作为方法实参

  In:只能用作方法实参,不能用作返回值类型。

  我觉得只要上面两个概念搞懂了,在自己敲下Demo就没有问题了。

 

本文参考:http://www.cnblogs.com/majiang/articles/2607990.html

  

以上是关于那些年搞不懂的"协变"和"逆变"的主要内容,如果未能解决你的问题,请参考以下文章

那些年搞不懂的术语概念:协变逆变不变体

那些年搞不懂的多线程同步异步及阻塞和非阻塞---多线程简介

那些年搞不懂的高深术语——依赖倒置?控制反转?依赖注入?面向接口编程

上周热点回顾(8.29-9.4)

Java进阶知识点2:看不懂的代码 - 协变与逆变

搞不懂的github