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

什么是协变与逆变

MSDN的解释:

https://msdn.microsoft.com/zh-cn/library/dd799517.aspx

协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型。

泛型类型参数支持协变和逆变,可在分配和使用泛型类型方面提供更大的灵活性。

一开始我总是分不清协变和逆变,因为MSDN的解释实在是严谨有余而易读不足。

其实从中文的字面上来理解这两个概念就挺容易的了:

"协变"即"协调的转变","逆变"即"逆向的转变"。

下面来一起看看详细的介绍吧

C#中的协变与逆变

一、使用协变(Covariance)和逆变(Contravariance )能够实现数组之间、委托实例和方法之间、泛型委托实例之间、泛型接口的变量和泛型类型的对象之间、泛型接口的变量之间的隐式转换;使用协变将允许使用比原指定类型派生程度更大(即更具体的)的类型,使用逆变将允许使用比原指定类型派生程度更小(即更不具体的)的类型;

1.协变和逆变都只支持引用类型,不支持值类型;

2.如果泛型接口或泛型委托的类型参数被声明为协变或逆变,则该泛型接口或泛型委托被称为变体(Variant);

二、数组只支持协变,即支持派生程度更大的类型的数组隐式转换为派生程度更小的类型的数组:

object[] myArray = new string[5];
IComparable[] myOtherArray = new string[5];

1.此操作不是类型安全的,给上述数组添加原数组不兼容的对象时会抛出异常ArrayTypeMismatchException:

//myArray[0] = 10; //此处10会被装箱为object类型,而object类型的对象不能隐式转换为string类型

2.由于值类型不支持协变和逆变,因此下面的转换是错误的:

//object[] myArray = new int[5];
//IComparable[] myOtherArray = new int[5];

三、委托支持协变和逆变,为匹配委托类型和方法签名提供更大的灵活性,不仅可以将签名完全匹配的方法分配给委托实例,还可以通过协变将返回值类型与委托类型的返回值类型相比派生程度更大的方法分配给委托实例;通过逆变将参数类型与委托类型的参数类型相比派生程度更小的方法分配给委托实例:

public class MyBaseClass { }
public class MyClass : MyBaseClass { }
public class MyDerivedClass : MyClass { }
public delegate MyClass MyDelegate(MyClass obj);
public delegate T MyDelegate<T>(T obj);
//对于给定的方法
public staitc MyDerivedClass MyFunc(MyBaseClass obj)
{
 return new MyDerivedClass();
}
//使用时:
MyDelegate myDelegate = MyFunc; //同时使用了协变和逆变
MyDelegate<MyClass> myDelegate = MyFunc;

四、在定义泛型委托时,可以通过将类型参数声明为逆变量或协变量来定义变体委托(Variant Delegate),从而使具有协变和逆变转换关系的不用类型的泛型委托实例之间进行隐式转换,使用关键字out修饰返回值的类型参数以支持协变,使用关键字in修饰参数的类型参数以支持逆变:

public delegate U MyDelegate<in T, out U>(T obj);
//使用时:
MyDelegate<MyClass,MyClass> myDelegate = MyFunc;
MyDelegate<MyBaseClass, MyDerivedClass> myOtherDelegate = MyFunc;
//隐式转换:
myDelegate = myOtherDelegate;

1.变体委托不支持合并,即Delegate.Combine方法需要委托的类型完全相同才能合并,不支持变体委托的转换;在运行时合并不同类型的变体委托会抛出异常System.ArgumentException

五、在定义泛型接口时,可以通过将类型参数声明为逆变量或协变量来定义变体接口(Variant Interface),协变允许方法的返回值类型比接口定义中返回值类型参数的派生程度更大,逆变允许方法的参数类型比接口定义中参数类型参数的派生程度更小;使用关键字out修饰返回值的类型参数以支持协变,使用关键字in修饰参数的类型参数以支持逆变:

public interface IMyInterface<in T, out U>
{
 U MyFunc();
 void MyFoo(T obj);
}
public class MyClass<T, U> : IMyInterface<T, U>
{
 public void MyFoo(T obj)
 {
 //do…
 }

}
//使用时:
IMyInterface<string, object> myObj = new MyClass<object, string>();
IMyInterface<object, string> myObj1 = new MyClass<object, string>();
myObj = myObj1;

1.实现变体接口的类型仍然是不可变量;

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

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

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

  • C#逆变与协变详解

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

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

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

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

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

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

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

  • C#中的协变与逆变小结

    一:什么是协变与逆变 协变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,逆变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型 只有泛型接口和泛型委托参数支持协变和逆变 二:引言 using System; using System.Collections.Generic; class MainClass { static void Main() { object o = "str"; List<object> oList = new Lis

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

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

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

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

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

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

  • 你了解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关键字表示逆变.二者只能在泛型接口或者委托中使用. 理解协变和逆变 看完上面的定义是不是一脸懵逼~~~.看不懂就对了,且定义语句的歧义性很大.让我们

随机推荐