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

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

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<Animal> actionAnimal = new Action<Animal>(a => {/*让动物叫*/ });
  2. Action<Dog> actionDog = 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

  

时间: 2024-10-02 05:24:32

那些年搞不懂的"协变"和"逆变"的相关文章

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

简述什么是协变性.逆变性.不变性 协变性,如:string->object (子类到父类的转换) 逆变性,如:object->string (父类到子类的转换) 不变性,基于上面两种情况,不可变.具体下面再做分析. 泛型委托的可变性 先使用框架定义的泛型委托Func和Action做例子(不了解的请戳) 协变:(string->object) Func<string> func1 = () => "农码一生"; Func<object> f

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

要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那么我们可以在任何时候使用MochaCoffee类的引用去替换Coffee类的引用(重写函数时,形参必须与重写函数完全一致,这是一处列外),而不会引发编译错误(至于会不会引发程序功能错误,取决于代码是否符合里氏替换原则). 简而言之,如果B类是A类的派生类,那么B类的引用可以赋值给A类的引用. 赋值的方式最常见

厘清泛型参数的协变与逆变

协变与逆变(CoVariant and ContraVariant),很多人是糊涂的,我也一直糊涂.其实,对协变与逆变概念糊涂,甚至完全不知道,对一般程序员也没有很大影响.不过,如果你想提高水平,想大概看懂.Net Framework类库中那些泛型接口与泛型类,想大概弄清楚Linq,这个概念还是需要搞清楚. 话又说回来,想弄清楚,其实还是挺费劲的. 如果你还糊涂着这两个概念,相信我,认真看完下面的文字,你会对泛型参数的协变与逆变有一个清晰的理解. 想透彻掌握协变.逆变的概念,首先需要对接口.委托

C# - 协变与逆变

public interface IFication<T>{    void Method1 ( T t );    T Method2();} public class Parent : IFication<Parent>{    public string Car="阿斯顿马丁";  // 父亲具有车子 public void Method1 ( Parent p )    {        Console.WriteLine ( p.Car );    }

协变和逆变

具体可以参考:<Effective Java>PECS 原则 (producser-extends, consumer-super) G[+A]类似一个生产者,提供数据.(大部分情况下称G为容器类型)G[-A] 是一个消费者,主要用来消费数据.(如上的 Equiv[-A] (其实就是个A => Boolean的函数,即Function1[-A, Boolean])) 同理函数的参数为何声明为逆变,返回值为协变就好理解了同理class G[+A]{def fun[B >: A](x:

Scala 协变 和 逆变

在Scala(以及其他许多编程语言)中,函数也是对象,可以使用.定义其他对象的地方,也可以使用.定义函数.Scala中的函数,具有apply方法的类的实例,就可以当做函数来使用.其中apply接受的参数就是函数的参数,而apply的返回值就是函数的返回值. 首先给出一个接受一个参数的函数的泛型定义. trait Function1[-T, +U] {   def apply(x: T): U } 这种函数接受一个参数,参数类型为泛型类型T,返回类型为泛型类型U.和其他支持泛型的语言一样,实际定义

泛型中协变和逆变

写在前面 今天讲的内容有点多,但是差不多都能听懂,稍微有点模糊的就是协变和逆变的概念,下面是我结合在网上看的资料整合而成的. 正文 msdn上的原话: 协变:是指能够使用比原始指定的派生类型的派生程度更小(不太确定)的类型 逆变:是指能够使用比原始类型的派生类型的派生程度更大(更具体)的类型 在方便理解的概念是: 协变:子类向父类转化,用于返回类型用out关键字 逆变:父类向子类转化的过程,用于方法参数类型用in关键字 协变的例子: 1 public class Person { } 2 3 p

面向对象设计——协变与逆变

在面向对象的设计中,我们一直追求一种结果,就是良好的复用性,基于这个理念,面向对象的设计中加入了协变与逆变(Covariance and Contravariance)两个概念,我们先来简单了解一下这两个概念. 简介: 协变:由子类向父类方向转变, 用out关键字标识 逆变:由父类向子类方向转变, 用in关键字 举例:Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是Dog,那么他必然是Animal.有一个获取宠物的方法要接受Dog参数,那么另一个接受Animal参数的方

变体(协变与逆变)

变体的引入是为了提高泛型类型的变量在赋值时可以对类型进行兼容性转换,以扩展泛型的灵活性.下面看个例子: public delegate void DoWork<T>(T arg); ........ DoWork<A> del1=delegate(A arg){//.......}; DoWork<B> del2=del1; B bb=new B(); del2(bb); 其中A ,B是两个类,B类继承A类,即:public class A{.....}