详解c# 协变和逆变

基本概念

协变:能够使用比原始指定的派生类型的派生程度更大(更具体)的类型。例如 IFoo<父类> = IFoo<子类>
逆变:能够使用比原始指定的派生类型的派生程度更新(更抽象)的类型。例如 IBar<子类> = IBar<父类>

关键字out和in

协变和逆变在泛型参数中的表现方式,out关键字表示协变,in关键字表示逆变。二者只能在泛型接口或者委托中使用。

理解协变和逆变

看完上面的定义是不是一脸懵逼~~~。看不懂就对了,且定义语句的歧义性很大。让我们大脑赶紧清空下!!首先记住一点明确的概念,类的多态展示一定是通过基类来表示,派生的具体类都是可转化为基类,而不能走相反的流程。
下面我们用代码直观的表现下协变和逆变。

public class Animal
{
  public void Eat()
  { }
}

public class Dog : Animal
{
  public void Run()
  {
  }
}

这是一段很简单的子类和父类的关系,我们进行一下简单的转化,应该很好理解,Dog子类可以用Animal父类展示,反过来则不可以,会编译错误。

    Dog dog = new Dog();
    Animal animal = dog;

    //error 编译错误
    //Dog dog2 = animal;

那么我们做一点变化。

    List<Dog> dogs = new List<Dog>();
    //error 编译错误
    //List<Animal> animals_2 = dogs;

    IEnumerable<Dog> dogs_2 = dogs;
    IEnumerable<Animal> animals = dogs_2;

感觉到一点问题没?Dog子类可以用Animal父类展示,使用List泛型就不可以了,但是IEnumerable泛型又可以。List<>和IEnumerable<>有什么不同?我们看下二者的定义即可发现端倪。

//IList定义
public interface IList<[NullableAttribute(2)] T> : ICollection<T>, IEnumerable<T>, IEnumerable
{}

//和IEnumerable定义
public interface IEnumerable<[NullableAttribute(2)] out T> : IEnumerable
{}

区别就在于 IEnumerable的泛型参数用了out协变标注,所以可以做正确的转换。 这里也可以理解出什么时候需要使用in、out关键字:当你设计带有泛型的基类且泛型类型可能存在扩展时,则需要考虑使用in或者out关键字修饰。
我们再看看官方的Action<>和Func<>类对协变和逆变的使用,先看定义:

public delegate void Action<[NullableAttribute(2)] in T>(T obj);

public delegate TResult Func<[NullableAttribute(2)] in T, [NullableAttribute(2)] out TResult>(T arg);

Action的泛型类型是入参,用in表示逆变,Func的第二个泛型类型TResult是出参,用out表示协变。
那么这样看起来对in、out关键字的认识就很简单明了了。看看转换示例:

    Action<Dog> action_dog = d => d.Run();
    Action<Animal> action_animal = a => a.Eat();

    //error 编译错误。in
    //Action<Animal> action_animal_2 = action_dog;
		//Action泛型多态化
    Action<Dog> action_dog_2 = action_animal;

    Func<int, Dog> func_dog = a => { return new Dog(); };
    Func<int, Animal> func_animal = a => { return new Animal(); };

		//Func泛型多态化
    Func<int, Animal> func_animal_2 = func_dog;
    //error 编译错误。out
    //Func<int, Dog> func_dog_2 = func_animal;

注意注释编译错误的语句,符合上面我们转换的规则。对于入参,扩展类可以替代基类参数输入,用in修饰;对于出参,扩展类可以替代基类返回输出,用out修饰。相反则都不可以。

最后简单总结下:

  • 什么是协变/逆变?不要去想官方定义!!!!只要记住out是协变,in是逆变即可。
  • 为什么需要使用协变-out、逆变-in。在泛型或委托中,如果不使用协变/逆变,那么泛型类型一个精确的、固定的某一类型。而使用协变/逆变的话,则泛型类型可以实现多态化。但必须区分入参使用in,出参使用out。

以上就是详解c# 协变和逆变的详细内容,更多关于c# 协变和逆变的资料请关注我们其它相关文章!

(0)

相关推荐

  • c#协变和逆变实例分析

    本文实例讲述了c#协变和逆变的原理及应用.分享给大家供大家参考.具体如下: 由子类向父类方向转变是协变,用out关键字标识,由父类向子类方向转变是逆变,用in关键字 协变和逆变的应用   一. 数组的协变 复制代码 代码如下: Animal[] animalArray = new Dog[]{}; 说明:声明的数组数据类型是Animal,而实际上赋值时给的是Dog数组:每一个Dog对象都可以安全的转变为Animal.Dog向Animal方法转变是沿着继承链向上转变的所以是协变   二. 委托中的

  • C#4.0新特性之协变与逆变实例分析

    本文实例讲述了C#4.0新特性的协变与逆变,有助于大家进一步掌握C#4.0程序设计.具体分析如下: 一.C#3.0以前的协变与逆变 如果你是第一次听说这个两个词,别担心,他们其实很常见.C#4.0中的协变与逆变(Covariance and contravariance)有了进一步的完善,主要是两种运行时的(隐式)泛型类型参数转换.简单来讲,所谓协变(Covariance)是指把类型从"小"升到"大",比如从子类升级到父类:逆变则是指从"大"变到

  • 一篇文章看懂C#中的协变、逆变

    1. 基本概念 官方:协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型.[MSDN] 公式: 协变:IFoo<父类> = IFoo<子类>: 逆变:IBar<子类> =  IBar<父类>: 暂时不理解没关系,您接着往下看. 2. 协变(Covariance) 1) out关键字 对于泛型类型参数,out 关键字可指定类型参数是协变的. 可以在泛型接口

  • C#逆变与协变详解

    该文章中使用了较多的 委托delegate和Lambda表达式,如果你并不熟悉这些,请查看我的文章<委托与匿名委托>.<匿名委托与Lambda表达式>以便帮你建立完整的知识体系. 在C#从诞生到发展壮大的过程中,新知识点不断引入.逆变与协变并不是C#独创的,属于后续引入.在Java中同样存在逆变与协变,后续我还会写一篇Java逆变协变的文章,有兴趣的朋友可以关注一下. 逆变与协变,听起来很抽象.高深,其实很简单.看下面的代码: class Person { } class Stud

  • C#中的协变与逆变深入讲解

    什么是协变与逆变 MSDN的解释: https://msdn.microsoft.com/zh-cn/library/dd799517.aspx 协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型. 泛型类型参数支持协变和逆变,可在分配和使用泛型类型方面提供更大的灵活性. 一开始我总是分不清协变和逆变,因为MSDN的解释实在是严谨有余而易读不足. 其实从中文的字面上来理解这两个概念就挺容易的

  • 你了解C#的协变和逆变吗,看完这篇就懂了

    从C# 4.0开始,泛型接口和泛型委托都支持协变和逆变,由于历史原因,数组也支持协变. 里氏替换原则:任何基类可以出现的地方,子类一定可以出现. 协变(out) 协变:即自然的变化,遵循里氏替换原则,表现在代码上则是任何基类都可以被其子类赋值,如Animal = Dog.Animal = Cat 使用out关键字声明(注意和方法中修饰参数的out含义不同) 被标记的参数类型只能作为方法的返回值(包括只读属性) 在没有协变时: abstract class Animal {} class Dog

  • 详解c# 协变和逆变

    基本概念 协变:能够使用比原始指定的派生类型的派生程度更大(更具体)的类型.例如 IFoo<父类> = IFoo<子类> 逆变:能够使用比原始指定的派生类型的派生程度更新(更抽象)的类型.例如 IBar<子类> = IBar<父类> 关键字out和in 协变和逆变在泛型参数中的表现方式,out关键字表示协变,in关键字表示逆变.二者只能在泛型接口或者委托中使用. 理解协变和逆变 看完上面的定义是不是一脸懵逼~~~.看不懂就对了,且定义语句的歧义性很大.让我们

  • Kotlin中协变、逆变和不变示例详解

    前言 Kotlin 泛型的基本语法类似于 Java ,不过出于型变安全,不支持 Java 中的<? extends T>,<?super T> 通配符型变约束,而是采用类似 C# 的 in,out 用于支持协变和逆变,这同时避免了处理子类型化,父类化的问题(即Java泛型中典型的List<T> 不是 List<Object>的子类型的问题): 基本的泛型语法可以参考官方中文文档:https://www.kotlincn.net/docs/reference/

  • 图文详解C#中的协变与逆变

    目录 前言 协变和逆变 总结 前言 这篇文章简单说说C#中的协变和逆变. 在C#编程中,由于存在类型之间的强制转换,很容易会出现所谓的类型可变性说法,存在协变.逆变.不变三种. 就比如前一篇文章介绍的泛型概念,如果创建了泛型类型的实例,编译器会接受泛型类型声明以及类型参数来创建构造类型.但是在日常使用过程中,我们可能会将派生类型分配给基类型的变量,有时候会出现错误. 这里就存在一个赋值兼容性问题. 每一个变量都有一种类型,可以将派生类对象的实例赋值给基类变量(好比之前子类声明的变量可以赋值给父类

  • 详析C#的协变和逆变

    目录 为啥C#需要协变和逆变? 协变(Covariance) 逆变(Contravariance) 前言: 在引用类型系统时,协变.逆变和不变性具有如下定义. 这些示例假定一个名为 Base 的基类和一个名为 Derived的派生类. Covariance 使你能够使用比原始指定的类型派生程度更大的类型. 你可以将 IEnumerable 的实例分配给 IEnumerable 类型的变量. Contravariance 使你能够使用比原始指定的类型更泛型(派生程度更小)的类型. 你可以将 Act

  • 基于.Net中的协变与逆变的深入分析

    关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是Dog,那么他必然是Animal.协变逆变正是利用继承关系 对不同参数类型或返回值类型 的委托或者泛型接口之间做转变.我承认这句话很绕,如果你也觉得绕不妨往下看看.如果一个方法要接受Dog参数,那么另一个接受Animal参数的方法肯定也可以接受这个方法的参数,这是Animal向Dog方向的转变是逆变.如

随机推荐