C# 泛型的协变和逆变
1. 可变性的类型:协变性和逆变性
可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用。如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量。协变和逆变是两个相互对立的概念:
- 如果某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变的
- 如果某个参数类型可以由其基类替换,那么这个类型就是支持逆变的。
2. C# 4.0对泛型可变性的支持
在C# 4.0之前,所有的泛型类型都是不变量——即不支持将一个泛型类型替换为另一个泛型类型,即使它们之间拥有继承关系,简而言之,在C# 4.0之前的泛型都是不支持协变和逆变的。
C# 4.0通过两个关键字:
out和in来分别支持以协变和逆变的方式使用泛型。
我们来看一段利用了协变类型参数的代码:
public class BaseClass
{
//...
}
public class DerivedClass : BaseClass
{
//...
}
下面我们利用协变类型参数,可以执行类似于普通的多态性的分配:
IEnumerable<DerivedClass> d = new List<DerivedClass>();
IEnumerable<BaseClass> b = d;
在上面的实例中,在C# 4.0之前是不能正常编译的,除了对赋值给基类集合时将子类集合做一个强制转换,但是在运行时仍然会抛出一个类型转换的异常。
下面我们再看一个关于逆变的实例代码:
Action<BaseClass> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<DerivedClass> d = b;
d(new DerivedClass());
在上面的示例中我们 Action<BaseClass> 类型的委托分配给类型 Action<DerivedClass> 的变量,根据逆变的定义我们可以知道 Action<T> 类型是支持逆变的。
为什么IEnumerable<T> 和 Action<T> 可以分别支持类型的协变和逆变呢?我们查看这两个类型在 .NET 中的定义:
//IEnumerable<T> 接口的定义(支持协变)
public interface IEnumerable<out T> : IEnumerable
//Action<T> 委托的定义(支持逆变)
public delegate void Action<in T>(T obj);
为了保证类型的安全,C#编译器对使用了 out 和 in 关键字的泛型参数添加了一些限制:
- 支持协变(
out)的类型参数只能用在输出位置:函数返回值、属性的get访问器以及委托参数的某些位置 - 支持逆变(
in)的类型参数只能用在输入位置:方法参数或委托参数的某些位置中出现。
3. C#中泛型可变性的限制
1. 不支持类的类型参数的可变性
只有接口和委托可以拥有可变的类型参数。in 和 out 修饰符只能用来修饰泛型接口和泛型委托。
2. 可变性只支持引用转换
可变性只能用于引用类型,禁止任何值类型和用户定义的转换,如下面的转换是无效的:
- 将
IEnumerable<int>转换为IEnumerable<object>——装箱转换 - 将
IEnumerable<short>转换为IEnumerable<int>——值类型转换 - 将
IEnumerable<string>转换为IEnumerable<XName>——用户定义的转换
3. 类型参数使用了 out 或者 ref 将禁止可变性
对于泛型类型参数来说,如果要将该类型的实参传给使用 out 或者 ref 关键字的方法,便不允许可变性,如:
delegate void someDelegate<in T>(ref T t)
这段代码编译器会报错。
4. 可变性必须显式指定
从实现上来说编译器完全可以自己判断哪些泛型参数能够逆变和协变,但实际却没有这么做,这是因为C#的开发团队认为:
必须由开发者明确的指定可变性,因为这会促使开发者考虑他们的行为将会带来什么后果,从而思考他们的设计是否合理。
5. 注意破坏性修改
在修改已有代码接口的可变性时,会有破坏当前代码的风险。例如,如果你依赖于不允许可变性的is或as操作符的结果,运行在.NET 4时,代码的行为将有所不同。同样,在某些情况下,因为有了更多可用的选项,重载决策也会选择不同的方法。所以在对已有代码引入可变性时要做好足够的单元测试以及防御措施。
6. 多播委托与可变性不能混用
下面的代码能够通过编译,但是在运行时会抛出 ArgumentException 异常:
Func<string> stringFunc = () => "";
Func<object> objectFunc = () => new object();
Func<object> combined = objectFunc + stringFunc;
这是因为负责链接多个委托的 Delegate.Combine方法要求参数必须为相同的类型。上面的示例我们可以修改成如下正确的代码:
Func<string> stringFunc = () => "";
Func<object> defensiveCopy = new Func<object>(stringFunc);
Func<object> objectFunc = () => new object();
Func<object> combined = objectFunc + defensiveCopy;
参考&扩展阅读
协变和逆变
泛型中的协变和逆变
委托中的协变和逆变
《深入理解C#》:13.3 接口和委托的泛型可变性
《Effective C#》:条目29:支持泛型协变和逆变
《CLR via C#》:12.5 委托和接口的逆变和协变泛型类型实参
C# 泛型的协变和逆变的更多相关文章
- .NET 4.0中的泛型的协变和逆变
转自:http://www.cnblogs.com/jingzhongliumei/archive/2012/07/02/2573149.html 先做点准备工作,定义两个类:Animal类和其子类D ...
- Java用通配符 获得泛型的协变和逆变
Java对应泛型的协变和逆变
- 转载.NET 4.0中的泛型的协变和逆变
先做点准备工作,定义两个类:Animal类和其子类Dog类,一个泛型接口IMyInterface<T>, 他们的定义如下: public class Animal { } public ...
- Kotlin泛型与协变及逆变原理剖析
在上一次https://www.cnblogs.com/webor2006/p/11234941.html中学习了数据类[data class]相关的知识,这次会学习关于泛型相关的东东,其中有关于泛型 ...
- Java泛型的协变与逆变
泛型擦除 Java的泛型本质上不是真正的泛型,而是利用了类型擦除(type erasure),比如下面的代码就会出现错误: 报的错误是:both methods have same erasure ...
- C#4.0泛型的协变,逆变深入剖析
C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...
- C# 泛型的协变和逆变 (转载)
1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量. 协变和逆变是两个相互对立的概念: ...
- C#-弄懂泛型和协变、逆变
脑图概览 泛型声明和使用 协变和逆变 <C#权威指南>上在委托篇中这样定义: 协变:委托方法的返回值类型直接或者间接地继承自委托前面的返回值类型; 逆变:委托签名中的参数类型继承自委托方法 ...
- c#-泛型、协变、逆变
泛型简单介绍: 可以使用泛型声明的元素:类.接口.方法.委托 泛型之前:泛型之前使用object封装不同类型的参数,缺点:性能差.运行时判断类型(不安全)...泛型是在编译期间转为实际类型副本,所以性 ...
随机推荐
- 使用替换shader渲染
相关函数: Camera.RenderWithShader(shader: Shader, replacementTag: string) 使用指定shader渲染,只影响一帧 Camera.SetR ...
- poj 3321 Apple Trie
/* poj 3321 Apple Trie 这道题的关键是如何将一个树建成一个一维数组利用树状数组来解题! 可以利用dfs()来搞定,我们在对一个节点深搜后,所经过的节点的数目就是该节点的子树的数目 ...
- hdu1548 A strange lift(bfs 或Dijkstra最短路径)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #d ...
- Mina、Netty、Twisted一起学(九):异步IO和回调函数
用过JavaScript或者jQuery的同学都知道,JavaScript特别是jQuery中存在大量的回调函数,例如Ajax.jQuery的动画等. $.get(url, function() { ...
- 《BI那点儿事》数据挖掘初探
什么是数据挖掘? 数据挖掘(Data Mining),又称信息发掘(Knowledge Discovery),是用自动或半自动化的方法在数据中找到潜在的,有价值的信息和规则. 数据挖掘技术来源于数据库 ...
- Prim算法(二)之 C++详解
本章是普里姆算法的C++实现. 目录 1. 普里姆算法介绍 2. 普里姆算法图解 3. 普里姆算法的代码说明 4. 普里姆算法的源码 转载请注明出处:http://www.cnblogs.com/sk ...
- Yii的学习(2)--数据访问对象 (DAO)
摘自Yii官网:http://www.yiiframework.com/doc/guide/1.1/zh_cn/database.dao Yii提供了强大的数据库编程支持.Yii数据访问对象(DAO) ...
- A/B 测试之前必须要了解的 10 件事
如今,转化率优化(CRO)已是营销人员必须具备的技能,并且与 ROI 直接挂钩.但是在优化网页的转化率方面又有太多因素要考量,如果你已经不堪其忧,请专心做一件事-- A/B 测试. A/B测试,即你设 ...
- Visual Studio远程调试监视器(MSVSMON.EXE)的32位版本不能用于调试64位进程或64位转储
在VS2013中调试Silverlight项目时,提示:无法附加.Visual Studio远程调试监视器(MSVSMON.EXE)的32位版本不能用于调试64位进程或64位转储.请改用64位版本. ...
- express 框架之session
一.什么是session? 最近在学习node.js 的express框架,接触到了关于session方面的内容.翻阅了一些的博客,学到了不少东西,发现一篇博文讲的很好,概念内容摘抄如下: Sessi ...