加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

那些年搞不懂的术语、概念:协变、逆变、不变体

发布时间:2020-12-15 04:44:23 所属栏目:百科 来源:网络整理
导读:简述什么是协变性、逆变性、不变性 协变性,如:string->object (子类到父类的转换) 逆变性,如:object->string (父类到子类的转换) 不变性,基于上面两种情况,不可变。具体下面再做分析。 泛型委托的可变性 先使用框架定义的泛型委托Func和Action做例

简述什么是协变性、逆变性、不变性

  • 协变性,如:string->object (子类到父类的转换)
  • 逆变性,如:object->string (父类到子类的转换)
  • 不变性,基于上面两种情况,不可变。具体下面再做分析。

泛型委托的可变性

先使用框架定义的泛型委托Func和Action做例子(

协变:object)

Func<> = () => <> func2 = ;

逆变:string)

Action<> = t =><> func4 = ;

上面代码没有任何问题。

接着我们自己定义委托试试:

我X,看人不来哦。为什么自定义的委托却不能协变呢。

我看看系统定义的Func到底和我们自定义的有什么不同:

TResult Func< TResult>();

多了一个out,什么鬼:

  • ()

完美!

那如果我们要实现逆变性呢:

直接逆变是不可行的,我们需要修改泛型类型参数:

我们发现整个委托参数都变了。本来的返回值,改成输入参数才行。

结论:

  • 输入参数->可逆变string])
  • 返回值->可协变object])

假设:如果泛型参数中既存在in又存在out改如何:

Tout MyFunc< Tin, Tout>(Tin obj);
MyFunc<,> str1 = t => <,> str2 = str1;string) MyFunc<,> str3 = str1;object) MyFunc<,> str4 = str1;

以上都是没有问题的。?

然后我们看看编译后的C#代码:

结论:

以上代码也可以直接写成:

(Tin obj); MyFunc<,> str5 = t => <,> str6 = t => <,> str7 = t => ;

泛型接口的可变性

接着看框架默认接口:

协变:父类)

IEnumerable<> list = List<><> list2 = list;

逆变: 子类)

IComparable<> list3 = <> list4 = list3;

接下来我们试试自定泛型接口:

首先定义测试类型、接口:

IMotion Run : IMotion

然后我们测试协变性:

同样我们需要把接口? IMotion?定义为? IMotion< T>?

IMotion<?T>{}
IMotion x = Run y = x;

如果我们要测试逆变性,则需要把? IMotion ?定义为? IMotion< T>?

IMotion< T>{}
IMotion x2 = Run y2 = x2;

泛型接口的逆变,编译后同样进行了强制转换:

当然,我们也可以直接写成:

IMotion y3 = Run();

不变性

从上面我们知道逆变性的代码编译后都会进行强制转换。假设:那我们不用out、in直接手动强制转换是否可以?:

IMotion Run : IMotion { }
IMotion x = Run y = x;

<span style="color: #008000;">//<span style="color: #008000;">逆变
IMotion x2 = <span style="color: #0000ff;">new Run<span style="color: #000000;">();
IMotion y2 = <span style="color: #ff0000;">(IMotion<span style="color: #000000;"><span style="color: #ff0000;">)x2;
IMotion y3 = <span style="color: #ff0000;">(IMotion)<span style="color: #0000ff;">new Run();

天才的我发现编译成功了,没有任何问题!且还可以同时协变、逆变??不对,真的天才了吗?我们运行试试:

看来我还是太单纯了,如果真的这么容易绕过去,Microsoft又何必去搞个out、in关键字。

对于同一个泛型参数,我们既想有协变性又想逆变性,咋办?答案是不可行。这就会出现第三种情况,既不可以协变又不可以逆变。称为不变性。

IMotion

上面我们测试过,代码直接强制转换是不能实现协变、逆变的。那么我们只能通过out、in来实现。如果现在我们在泛型参数添加out或in属性会如何?:

我们发现out和in都不能用。在用out时,有个传入参数为泛型? Match(T t)?的方法。使用in时,有个返回参数为泛型??的方法。现在就出现了是矛更锋利还是盾更坚硬的问题了。

最后结果是:都不能用,既不能协变,也不能逆变。此为不变体

小知识:

C#4.0之前??、??、??等接口都不支持可变性,在4.0及之后才支持。因为4.0之前定义的泛型接口没有添加out、in关键字,有兴趣可以切换版本看看。

延伸思考

为什么in[输入参数]就只能逆变?分析如下:

{ ; <span style="color: #008000;">//<span style="color: #008000;">运动
<span style="color: #0000ff;">public
<span style="color: #0000ff;">interface
IMotion<<span style="color: #0000ff;">in
T><span style="color: #000000;">
{
<span style="color: #0000ff;">void
<span style="color: #000000;"> Match(T t);
}
<span style="color: #008000;">//
<span style="color: #008000;">跑步

<span style="color: #0000ff;">public
<span style="color: #0000ff;">class
Run : IMotion<span style="color: #000000;">
{
<span style="color: #0000ff;">public
<span style="color: #0000ff;">void
<span style="color: #000000;"> Match(T t)
{
<span style="color: #008000;">//
<span style="color: #008000;">假设中间有很多逻辑.....
<span style="color: #000000;"> }
}

为什么out[返回值]只能协变?分析如下:

Salary { ; <span style="color: #008000;">//<span style="color: #008000;">运动
<span style="color: #0000ff;">public
<span style="color: #0000ff;">interface
IMotion<<span style="color: #ff0000;">out
T><span style="color: #000000;">
{
<span style="color: #ff0000;">T Show();
<span style="color: #008000;">//
<span style="color: #008000;">void Match(T t);

<span style="color: #000000;">}
<span style="color: #008000;">//
<span style="color: #008000;">跑步

<span style="color: #0000ff;">public
<span style="color: #0000ff;">class
Run : IMotion<span style="color: #000000;">
{
<span style="color: #0000ff;">public
<span style="color: #000000;"> T Show()
{
<span style="color: #0000ff;">return <span style="color: #0000ff;">default<span style="color: #000000;">(T);
}
<span style="color: #008000;">//<span style="color: #008000;">public void Match(T t)
<span style="color: #008000;">//<span style="color: #008000;">{
<span style="color: #008000;">// <span style="color: #008000;">//<span style="color: #008000;">假设中间有很多逻辑.....
<span style="color: #008000;">//<span style="color: #008000;">}
}

这里有两个关键点:

  • 传入参数(in)是把参数当成父类来用,显然可以逆变(子类当成父类来用[]),但是却不可以把父类当子类来用
  • 返回值(out)返回值类型用父类来接收,显然可以协变(父类可以接收一切子类),但却不可用子类接收父类数据

。。。是不是有点越想越头晕,想不明白就慢慢想。自己动动手。

如果实在想的头大,就把它当成是乌龟的屁股吧,知道是C#做的一种安全限制!

总结

关于泛型接口、泛型委托的可变性:

  • -> 比较和谐正常的变化 -> 子类转父类 [如 string转object] -> ?[返回值]
  • -> 逆天的变化 -> 父类转子类 [如object转string] -> ?[传入参数] ?
  • 所谓的逆变,会在编译后的C#代码中进行强制类型转换。
  • 示例:
    • IEnumerable<> list = new List<>(); ?IEnumerable<> list2 = list;?IEnumerable<> list2 = new List<>(); ?

    • IComparable<> list3 = null;IComparable<> list4 = list3; ?IComparable list4 = )?

注意:

  • 不支持类的类型参数的可变性
  • 只有泛型接口和泛型委托可以拥有可变的类型参数(out、in)
  • 可变性只支持引用转换。(不能用于值类型)
  • 类型参数使用了 out 或者 ref 将禁止可变性

好了,今天就到这里。没啥高深的技术知识,主要为理解协变、逆变、不变体等术语和概念。

本文已同步至索引目录:《》

同类文章推荐:

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读