C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
在《上篇》中我们揭示了“缺省参数”的本质,现在我们接着来谈谈C#4.0中另一个重要的新特性:协变(Covariance)与逆变(Contravariance)。对于协变与逆变,大家肯定不会感到陌生,但是我相信有很多人不能很清晰地说出他们之间的区别。我希望通过这篇文章能够让读者更加深刻的认识协变与逆变。但是也不排除另一种可能,那就是读者这篇文章你对这两个概念更加模糊。文章一些内容仅代表个人观点,如有不妥,还望指正。
目录
一、两个概念:强类型与弱类型
二、委托中的协变与逆变的使用
三、接口中的协变与逆变的使用
四、从Func T,TResult 看协变与逆变的本质
五、逆变实现了“算法”的重用
为了后面叙述方便,我现在这里自定义两个概念:强类型和弱类型。在本篇文章中,强类型和弱类型指的是两个具有直接或者间接继承关系的两个类。如果一个类是另一个类的直接或者间接基类,那么它为弱类型,直接或者间接子类为强类型。后续的介绍中会用到的两个类Foo和Bar先定义在这里。Bar继承自Foo。Foo是弱类型,而Bar则是强类型。
有了强类型和弱类型的概念,我们就可以这样的定义协变和逆变:如果类型TBar是基于强类型Bar的类型(比如类型参数为Bar的泛型类型,或者是参数/返回值类型为Bar的委托),而类型TFoo是基于弱类型Foo的类型,协变就是将TBar类型的实例赋值给TFoo类型的变量,而逆变则是将TFoo类型的实例赋值给TBar类型的变量。
二、委托中的协变与逆变的使用协变和逆变主要体现在两个地方:接口和委托,先来看看在委托中如何使用协变和逆变。现在我们定义了如下一个表示无参函数的泛型委托Function T ,类型参数为函数返回值的类型。泛型参数之前添加了一个out关键字表示T是一个协变变体。那么在使用过程中,基于强类型的委托Fucntion Bar 实例就可以赋值给基于弱类型的委托Fucntion Foo 变量。
接下来介绍逆变委托的用法。下面定义了一个名称为Operate的泛型委托,接受一个具有泛型参数类型的参数。在定义泛型参数前添加了in关键字,表示T是一个基于逆变的变体。由于使用了逆变,我们就可以将基于弱类型的委托Operate Foo 实例就可以赋值给基于强类型的委托Operate Bar 变量。
三、接口中的协变与逆变的使用
接下来我们同样通过一个简单的例子来说明在接口中如何使用协变和逆变。下面定义了一个继承自 IEnumerable T 接口的IGroup out T 集合类型,和上面一样,泛型参数T之前的out关键字表明这是一个协变。既然是协变,我们就可以将一个基于强类型的委托IGroup Bar 实例就可以赋值给基于弱类型的委托IGroup Foo 变量。
下面是一个逆变接口的例子。首先定义了一个IPaintable的接口,里面定义了一个可读写的Color属性,便是实现该接口的类型的对象具有自己的颜色,并可以改变颜色。类型Car实现了该接口。接口IBrush in T 定义了一把刷子,泛型类型需要实现IPaintable接口,in关键字表明这是一个逆变。方法Paint用于将指定的对象粉刷成相应的颜色,表示被粉刷的对象的类型为泛型参数类型。Brush T 实现了该接口。由于IBrush in T 定义成逆变,我们就可以将基于弱类型的委托IBrush IPaintable 实例就可以赋值给基于强类型的委托IBrush Car 变量。
四、从Func T,TResult 看协变与逆变的本质
接下来我们来谈谈协变和逆变的本质区别是什么。在这里我们以我们非常熟悉的一个委托Func T, TResult 作为例子,下面给出了该委托的定义。我们可以看到Func T, TResult 定义的两个泛型参数分别属于逆变和协变。具体来说输入参数类型为逆变,返回值类型为协变。
再重申以下这句话“输入参数类型为逆变,返回值类型为协变”。然后,你再想想为什么逆变用in关键字,而协变用out关键字。这两个不是偶然,实际上我们可以将协变/逆变与输出/输入匹配起来。
我们再从另一个角度来理解协变与逆变。我们知道接口代表一种契约,当一个类型实现一个接口的时候就相当于签署了这份契约,所以必须是实现接口中所有的成员。实际上类型继承也属于一种契约关系,基类定义契约,子类“签署”该契约。对于类型系统来说,接口实现和类型继承本质上是一致的。契约是弱类型,签署这份契约的是强类型。
将契约的观点应用在委托上面,委托实际上定义了一个方法的签名(参数列表和返回值),那么参数和返回值的类型就是契约,现在的关键是谁去履行这份契约。所有参数是外界传入的,所以基于参数的契约履行者来源于外部,也就是被赋值变量的类型,所以被赋值变量类型是强类型。而对于代理本身来说,参数是一种输入,也就是一种采用in关键字表示的逆变。
而对于委托的返回值,这是给外部服务的,是委托自身对外界的一种承诺,所以它自己是契约的履行着,因此它自己应该是强类型。相应地,对于代理本身来说,返回值是一种输出,也就是一种采用out关键字定义的协变。
也正式因为这个原因,对于一个委托,你不能将参数类型定义成成协变,也不能将返回类型定义成逆变。下面两中变体定义方式都是不能通过编译的。
说到这里,我想有人要问一个问题,既然输入表示逆变,输出表示协变,委托的输出参数应该定义成协变了?非也,实际上输出参数在这里既输出输出,也输出输入(毕竟调用的时候需要指定一个对应类型的对象)。也正是为此,输出参数的类型及不能定义成协变,也不能定义成逆变。所以下面两种变体的定义也是不能通过编译的。
虽然这里指介绍了关于委托的协变与逆变,上面提到的契约和输入/输出的关系也同样适用于基于接口的协变与逆变。你自己可以采用这样的方式去分析上面一部分我们定义的IGroup Foo 和IBrush in T 。
五、逆变实现了“算法”的重用实际上关系协变和逆变体现出来的编程思想,还有一种我比较推崇的说法,那就是:协变是继承的体现,而逆变体现的则是多态(可以参考idior的文章《Covariance and Contravariance》)。实际上这与上面分析的契约关系本质上是一致的。
关于逆变,在这里请容我再啰嗦一句:逆变背后蕴藏的编程思想体现出了对算法的重用——我们为基类定义了一套操作,可以自动应用于所有子类的对象。
刚刚看了园友施凡的文章,写得很好,有兴趣的可以读读《.NET 4.0中的泛型协变和反变》。
作者:蒋金楠微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 原文链接
java面向对象三大特性,多态篇 1.概述 多态是同一个行为具有多个不同表现形式或形态的能力。 多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
java泛型特性,你了解多少? 对java泛型特性的了解,很多时候是从集合对象接触到的,今天小编带大家一起去深入的了解泛型的缘由和使用方式!
知其然,知其所以然:TypeScript 中的协变与逆变 ## 前言 在前一篇文章《淘宝店铺 TypeScript ESLint 规则集考量》中,我们提到了这一条规则:**method-signature-style**,它的作用是对 interface 中不同的函数声明方式进行约束,这里的声明方式主要有两种,_method_ 与 _property_,区别如下: ```typescript // method interface T1 {
教你利用Lambda将代码化繁为简 | 带你学《Java面向对象编程》之八十六 有没有觉得你的代码越来越繁琐呢?为了简化代码,JDK1.8推出了支持函数式编程的Lambda表达式。本节简单介绍了Lambda表达式的一些基本信息。
相关文章
- 【转】C#类似Jquery的html解析类HtmlAgilityPack基础类介绍及运用
- 【转载】初学C#编程的注意事项
- C#联合halcon开发多相机-直线拟合【附部分源码】
- 让我们再为C#异步编程Async正名
- 5天玩转C#并行和多线程编程 —— 第四天 Task进阶
- 5天玩转C#并行和多线程编程 —— 第三天 认识和使用Task
- 5天玩转C#并行和多线程编程 —— 第一天 认识Parallel 转载 https://www.cnblogs.com/yunfeifei/p/3993401.html
- [JS] ECMAScript 6 - Variable : compare with c#
- C# 解压及压缩文件源代码
- C#编程思想(持续更新)
- C#编程:如何理解委托-2
- c#编程:事件delegate学习笔记-3
- C#多线程编程实战(原书第2版)
- C#中相关结构的用法及用途
- C#图像处理-OpenCVSharp教程(十五) OpenCVSharp形态学处理(一):膨胀、腐蚀
- C# Socket编程笔记
- C#钩子类 几乎捕获键盘鼠标所有事件
- C#学习-继承中的构造方法(父类中写上默认的无参构造函数+子类中调用父类中有参的构造方法)
- C#自定义控件编程轻松入门(2)
- C#自定义控件编程轻松入门(1)
- c#摄像头编程实例 (转)
- .NET(C#) HttpClient单例(Singleton)和每次请求new HttpClient对比
- .NET(C#) System.Linq中实现多列group by(分组)的示例代码
- C# - Sql数据类型的对应关系
- C#设计模式系列:代理模式(Proxy)
- 复习篇_C#_编程概念(1)_异步编程(3)
- 复习篇_C#_编程概念(1)_异步编程(2)
- 韦_恩带你用好async/await异步多线程(C#5.0引入的特性)
- 2019-8-31-C#-通过-probing-指定-dll-寻找文件夹
- 2019-8-31-C#-转换类型和字符串
- C# 通过编程的方法在桌面创建回收站快捷方式
- C# 将 Begin 和 End 异步方法转 task 异步
- C# json 转 xml 字符串