c#4.0中的不变(invariant)、协变(covariant)、逆变(contravariant)小记
不变/协变/逆变,4.0中的这几个概念越念越象绕口令,如果单纯死记硬背,就算记住了,时间长了还是会忘记的。
园子里已经有不少高手撰文写过这个话题:比如“装配脑袋”的NET 4.0中的泛型协变和反变 (2008年他就已经搞明白了这个概念)、偶像Artech的“C# 4.0新特性-"协变"与"逆变"以及背后的编程思想” 以及1-2-3的 协变(Covariance)和逆变(Contravariance)的十万个为什么
这里只是从应用的角度,简单记录一下:
从.net3.5开始,System命名空间里就定义了一个泛型委托,原型如下:
public delegate TResult Func<T, TResult>(T arg);
即:输入一个泛型参数T,返回一个泛型结果TResult
假设有以下代码:
using System;
namespace in_co_contra_variant
{
class Program
{
public static void Main(string[] args)
{
Func<object, ArgumentException> fn1 = Test;
ArgumentException e1 = fn1("aaaaa");
Func<string, Exception> fn2 = null;
fn2 = fn1;
Exception e2 = fn2("bbbbb");
Console.WriteLine("e1:{0}te2:{1}", e1.Message, e2.Message);
Console.Read();
}
public static ArgumentException Test(object obj)
{
return new ArgumentException(obj.ToString());
}
}
}
在.net3.5环境下编译会报错:11行 fn2=fn1 这里会提示
Cannot implicitly convert type 'System.Func<object,System.ArgumentException>' to 'System.Func<string,System.Exception>'
即:无法隐式将System.Func<object,System.ArgumentException>转换成System.Func<string,System.Exception>
说得更白一点,4.0以前的泛型委托,泛型参数一旦在实例使用过程中明确为具体类型后,是不能隐式自动转换成其它类型的,哪怕类型是兼容的(按道理来讲,fn1中的输入参数类型为object,由于string是继承自object的,所以能用object的地方,string应该是能用的;同理:fn1中(返回)输出参数类型ArumentException继承自Exception,所以返回类型ArgumentException可以向上的转化为Exception不会有任何问题,所以说fn1中的参数类型与fn2中的参数类型是安全兼容的,但是编译回不允许),这种不允许泛型参数类型变化的特点,称为不变性(invariant).
而在4.0中,上面的代码可正常编译运行,如果研究下4.0中Func中的原型,会发现多了二个关键字:
public delegate TResult Func<in T, out TResult>(T arg);
即:在输入参数T前加了一个in,而在输出参数(也就是返回参数)前加了一个out.
这样编译器就能自动将T隐式转化为T的子类,而返回类型TResult也能自动隐式转化为它的父类。
说穿了就是OOP中的一个常理:子类与父类的继承关系,其实就是is a的关系,所以任何能用父类做为输入参数的地方,当然也能用子类作为替换(子承父业);而任何返回子类的地方,当然也能安全的向上转行为父类.(儿子是人类,父母当然也是人类,不可能是畜生,呵)
这时,我们称T为逆变(ContraVariant)量,而TResult则为协变(CoVariant)量。记忆方法:向上转型称协变(因为这种转型肯定是安全的,比较“和谐”),向下转型称逆变(因为不一定能转型成功,有出错的可能,称逆变)
最后:in,out这二个关键字不仅能用于泛型委托,同样也适用于泛型接口(比如4.0中的IEnumerable接口)
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
- 【干货】一种直观的方法认识梯度下降
- 漫谈Java IO之普通IO流与BIO服务器
- 浅谈强化学习的方法及学习路线
- 【亲测有效】Win10家庭版Microsoft Edge页面出现乱码的两种解决方案及gpedit.msc命令无法使用的解决策略
- Fiddler抓包7-post请求(json)
- Selenium2+python自动化56-unittest之断言(assert)
- 长文 | 手把手教你如何使用python进行数据分析(最好将文章代码自己码一遍)
- 回归与梯度下降法及实现原理
- 【宅男宅女们的福音】电影天堂最新电影爬取及搜索脚本
- 把模块有关联的放在一个文件夹中 在python2中调用文件夹名会直接失败 在python3中调用会成功,但是调用不能成功的解决方案
- numpy用法小结
- 凯撒密码加解密及破解实现原理
- linux bash Shell脚本经典 Fork炸弹演示及命令详解
- python易错盲点排查之+=与+的区别分析以及一些赋值运算踩过的坑
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- ZooTeam 拍了拍你,来看看如何设计动态化表单
- C# 继承 基类和派生类基类的初始化C# 多重继承
- Go语言ORM-gorm学习笔记(一)
- Etcd+Prometheus+Grafana
- 梳理 6 项 webpack 的性能优化
- 第2章 Jenkins Server的安装部署方式
- 在MacOSX机器上设置JenkinsCI服务器
- springboot2配置文件定义${user.name}内容失效问题探究
- 重学Javascript之数据类型
- JPEG/Exif/TIFF格式解读(1):JEPG图片压缩与存储原理分析
- 聊聊dubbo-go的ProviderAuthFilter
- 仅需四步,写一个springboot starter
- 重学Javascript之类型转换
- Spring Cloud 微服务(九)- 集成 Spring Boot Admin
- 聊聊dubbo-go的RPCInvocation