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

所属分类: 软件编程 / C#教程 阅读数: 86
收藏 0 赞 0 分享

什么是协变与逆变

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.实现变体接口的类型仍然是不可变量;

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

更多精彩内容其他人还在看

C#获取并修改文件扩展名的方法

这篇文章主要介绍了C#获取并修改文件扩展名的方法,实例分析了C#编程方式修改文件扩展名的技巧,涉及Path类的使用方法,需要的朋友可以参考下
收藏 0 赞 0 分享

C#遍历操作系统下所有驱动器的方法

这篇文章主要介绍了C#遍历操作系统下所有驱动器的方法,涉及C#中DriveInfo类GetDrivers方法的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C#遍历指定目录下所有文件的方法

这篇文章主要介绍了C#遍历指定目录下所有文件的方法,实例分析了C#中GetFiles()方法遍历文件的使用技巧,非常具有实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C#拷贝文件简单实现方法

这篇文章主要介绍了C#拷贝文件简单实现方法,主要分析了FileInfo类中CopyTo方法针对文件复制的操作技巧,非常简单实用,需要的朋友可以参考下
收藏 0 赞 0 分享

C#获取文件相关信息的方法

这篇文章主要介绍了C#获取文件相关信息的方法,涉及C#中FileInfo类的相关使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C#交错数组用法实例

这篇文章主要介绍了C#交错数组用法,较为详细的分析了交错数组的概念、用法并实例分析了交错数组的使用技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

C#实现在前端网页弹出警告对话框(alert)的方法

这篇文章主要介绍了C#实现在前端网页弹出警告对话框(alert)的方法,涉及C#通过自定义函数调用window.alert方法弹出对话框的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C#实现控制Windows系统关机、重启和注销的方法

这篇文章主要介绍了C#实现控制Windows系统关机、重启和注销的方法,涉及C#调用windows系统命令实现控制开机、关机等操作的技巧,非常简单实用,需要的朋友可以参考下
收藏 0 赞 0 分享

C#读取Excel并转化成XML的方法

这篇文章主要介绍了C#读取Excel并转化成XML的方法,实例分析了C#操作Excel及XML文件的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C#创建一个Word并打开的方法

这篇文章主要介绍了C#创建一个Word并打开的方法,实例分析了C#操作word的常用技巧,非常具有实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多