当前位置: 首页 > news >正文

理解 C# 泛型接口中的协变与逆变(抗变)

最近在看《C#高级编程(第九版)》这本书,看到了泛型接口这章。其中关于协变和逆变没太理解,讲得有点坑爹,网上查了许多资料,总算(感觉)弄清楚了,来这里记录一下。

一、协变和逆变是什么?

先从字面上理解 协变(Covariance)逆变(Contravariance)

co- 是英文中表示“协同”、“合作”的前缀。协变 的字面意思就是 “与变化的方向相同”
contra- 是英文中表示“相反”的前缀,逆变 的字面意思就是是 “与变化方向相反”

那么问题来了,挖掘机技术哪家强?这里的 变化方向 指的是什么?

C# 中对于对象(即对象引用),仅存在一种隐式类型转换,即 子类型的对象引用到父类型的对象引用的转换。这里的变化指的就是这种 子->父 的类型转换。

object o = "hello";
//string (子类)类型的引用转换为 object (父类)类型的引用

协变逆变虽然从名字上看是两个完全相反的转换,但其实只是“子类型引用到父类型引用”这一过程在函数中使用的 两个不同阶段 而已,接下来将详细说明这点。

二、使用函数的不同阶段发生的类型转换

假设有一函数,接收 object 类型的参数,输出 string 类型的返回值:

string Method(object o)
{
    return "abc";
}

那么在Main函数中我们可以这样调用它:

string s = "abc";
object o = Method(s);

注意,这里发生了两次隐式类型转换:

  1. 在向函数输入时,参数 sstring 类型转换为 object 类型

  2. 在函数输出(返回)时,返回值 由 string 类型转换为 object 类型

我们这里可以看作是函数签名可发生变换(不论函数的内容,不影响结果):

  1. string Method(object o) 可变换为 string Method(string o)

  2. string Method(string o) 可变换为 object Method(string o)

也就是说,在函数输入时,函数的 输入类型 可由 object 变换为 string父->子
在函数输出时,函数的 输出类型 可由string变换为object子->父

三、理解泛型接口中的 inout参数

没有指定inout的情况

假设有一泛型接口,并且有一个类实现了此接口:

interface IDemo<T>
{
    T Method(T value);
}
public class Demo : IDemo<string>
{
    //实现接口 IDemo<string>

    public string Method(string value)
    {
        return value;
    }
}

Main函数中这样写:

IDemo<string> demoStr = new Demo();
IDemo<object> demoObj = demoStr;

上面的这段代码中的第二行包含了一个假设:

IDemo<string> 类型能够隐式转换为 IDemo<object> 类型

这乍看上去就像“子类型引用转换为父类型引用” 一样,然而很遗憾,他们并不相同。假如可以进行隐式类型转换,那就意味着:

string Method(string value) 能转换为 object Method(object value)

从上一节中我们知道,在函数这输入和输出阶段,其类型可变化方向是不同的。
所以在C#中,要想应用泛型接口类型的隐式转换,需要讨论“输入”和“输出”两种情况。

接口仅用于输出的情况,协变

interface IDemo<out T>
{
    //仅将类型 T 用于输出
    T Method(object value);
}

public class Demo : IDemo<string>
{
    //实现接口
    public string Method (object value)
    {
        //别忘了类型转换!
        return value.ToString();
    }
}    

Main函数中这样写:

IDemo<string> demoStr = new Demo();
IDemo<object> demoObj = demoStr;

可将 string Method (object value) 转换为 object Method (object value)
即可将 IDemo<string> 类型转换为 IDemo<object> 类型。
仅从泛型的类型上看,这是 “子->父” 的转换,与第一节中提到的转换方向相同,称之为“协变”

接口仅用于输入的情况,逆变

同理我们可以给 T 加上 in 参数:

interface IDemo<in T>
{
    //仅将类型 T 用于输入
    string Method(T value);
}

public class Demo : IDemo<object>
{
    //实现接口
    public string Method (object value)
    {
        return value.ToString();
    }
}    

Main函数中这样写:

IDemo<object> demoObj = new Demo();
IDemo<string> demoStr = demoObj;

这里可将 string Method (object value) 转换为 string Method (string value)
即可将 IDemo<object> 类型转换为 IDemo<string> 类型。
仅从泛型的类型上看,这是 “父->子” 的转换,与第一节中提到的转换方向相反,称之为“逆变”,有时也译作“抗变”或“反变”。

四、总结

以上只讨论了协变与逆变在方法中的情况,其实在属性中情况也相类似,不再说明。

可能大家也发现了,所谓“协”与“逆”都是只是一种表象,其内在本质为同一过程。

“协变”与“逆变”中的“协”与“逆”表示泛型接口在将类型参数仅用于输入或输出的情况下,其类型参数的隐式转换所遵循的规律。

协变

当泛型接口类型仅用于输出(使用关键词 out),其类型参数隐式转换所遵循的规律与对象引用的类型转换规律相同,称之为“协变”

逆变

当泛型接口类型仅用于输入(使用关键词 in),其类型参数隐式转换所遵循的规律与对象引用的类型转换规律相反,称之为“逆变”、“抗变”或“反变”。


=。= 感觉强类型语言有点坑爹啊

相关文章:

  • iOS学习之iOS沙盒(sandbox)机制和文件操作(二)
  • web前段开发工程师如何修成正果?
  • arguments的一些小技巧
  • [\u4e00-\u9fa5] //匹配中文字符
  • js Array 交集 并集 差集 去重
  • 难道生活复杂就那么好玩吗?
  • HTML中设置input等文本框为不可操作
  • 重置Mysql密码SOP
  • Centos 系统禁ping设置
  • 计算机网络--静态路由
  • 白话 Ruby 与 DSL 以及在 iOS 开发中的运用
  • python re正则表达式模块
  • WTL之VS2013环境搭建
  • c语言编写经验逐步积累4
  • Session 和 Cookie 机制
  • Google 是如何开发 Web 框架的
  • [译] 怎样写一个基础的编译器
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • 4个实用的微服务测试策略
  • Druid 在有赞的实践
  • PHP 小技巧
  • Rancher-k8s加速安装文档
  • Redis 中的布隆过滤器
  • SQL 难点解决:记录的引用
  • 百度小程序遇到的问题
  • 经典排序算法及其 Java 实现
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 嵌入式文件系统
  • 如何设计一个微型分布式架构?
  • kubernetes资源对象--ingress
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • #pragma multi_compile #pragma shader_feature
  • $(function(){})与(function($){....})(jQuery)的区别
  • (AtCoder Beginner Contest 340) -- F - S = 1 -- 题解
  • (js)循环条件满足时终止循环
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (补)B+树一些思想
  • (二)斐波那契Fabonacci函数
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (考研湖科大教书匠计算机网络)第一章概述-第五节1:计算机网络体系结构之分层思想和举例
  • (十)【Jmeter】线程(Threads(Users))之jp@gc - Stepping Thread Group (deprecated)
  • (转)IOS中获取各种文件的目录路径的方法
  • **PHP分步表单提交思路(分页表单提交)
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • .NET设计模式(2):单件模式(Singleton Pattern)
  • :not(:first-child)和:not(:last-child)的用法
  • []sim300 GPRS数据收发程序
  • [20150904]exp slow.txt
  • [④ADRV902x]: Digital Filter Configuration(发射端)
  • [Android开源]EasySharedPreferences:优雅的进行SharedPreferences数据存储操作