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

本文介绍了C#中泛型接口的协变和逆变概念,通过实例解释了如何在函数的输入和输出阶段理解这两种转换,并展示了在接口中使用`in`和`out`关键字实现协变和逆变的情况。文章帮助读者理解这些概念如何影响接口类型的隐式转换。
摘要由CSDN通过智能技术生成

最近在看《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);

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

在向函数输入时,参数 s 由 string 类型转换为 object 类型

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

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

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

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

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

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

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

没有指定in、out的情况

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

interface IDemo

{

T Method(T value);

}

public class Demo : IDemo

{

//实现接口 IDemo

public string Method(string value)

{

return value;

}

}

在Main函数中这样写:

IDemo demoStr = new Demo();

IDemo demoObj = demoStr;

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

IDemo 类型能够隐式转换为 IDemo 类型

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

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

从上一节中我们知道,在函数这输入和输出阶段,其类型可变化方向是不同的。

所以在C#中,要想应用泛型接口类型的隐式转换,需要讨论“输入”和“输出”两种情况。

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

interface IDemo

{

//仅将类型 T 用于输出

T Method(object value);

}

public class Demo : IDemo

{

//实现接口

public string Method (object value)

{

//别忘了类型转换!

return value.ToString();

}

}

在Main函数中这样写:

IDemo demoStr = new Demo();

IDemo demoObj = demoStr;

可将 string Method (object value) 转换为 object Method (object value)

即可将 IDemo 类型转换为 IDemo 类型。

仅从泛型的类型上看,这是 “子->父” 的转换,与第一节中提到的转换方向相同,称之为“协变”。

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

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

interface IDemo

{

//仅将类型 T 用于输入

string Method(T value);

}

public class Demo : IDemo

{

//实现接口

public string Method (object value)

{

return value.ToString();

}

}

在Main函数中这样写:

IDemo demoObj = new Demo();

IDemo demoStr = demoObj;

这里可将 string Method (object value) 转换为 string Method (string value)

即可将 IDemo 类型转换为 IDemo 类型。

仅从泛型的类型上看,这是 “父->子” 的转换,与第一节中提到的转换方向相反,称之为“逆变”,有时也译作“抗变”或“反变”。

四、总结

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

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

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

协变

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

逆变

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

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值