news 2026/7/2 2:45:48

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
那些年搞不懂的术语、概念:协变、逆变、不变体

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

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

泛型委托的可变性

先使用框架定义的泛型委托Func和Action做例子(不了解的请戳)

协变:(string->object)

Func<string> func1 = () => "农码一生"; Func<object> func2 = func1;

逆变:(object->string)

Action<object> func3 = t => { }; Action<string> func4 = func3;

上面代码没有任何问题。

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

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

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

public delegate TResult Func<out TResult>();

多了一个out,什么鬼:

  • out:对于泛型类型参数,out 关键字指定该类型参数是协变的。 可以在泛型接口和委托中使用 out 关键字。(来源)
  • in:对于泛型类型参数,in 关键字指定该类型参数是逆变的。 可以在泛型接口和委托中使用 in 关键字。(来源)

那么我们可以修改自定义委托:

完美!

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

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

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

结论:

  • in->输入参数->可逆变(父类到子类的转变[如 object->string])
  • out->返回值->可协变(子类到父类的转变[如 string->object])

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

delegate Tout MyFunc<in Tin, out Tout>(Tin obj);
MyFunc<object, string> str1 = t => "农码一生"; MyFunc<string, string> str2 = str1;//第一个泛型的逆变(object->string) MyFunc<object, object> str3 = str1;//第二个泛型的协变(string->object) MyFunc<string, object> str4 = str1;//第一个泛型的逆变和第二个泛型的协变

以上都是没有问题的。

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

结论:

  • 所谓的逆变其实只是编译后进行了强制类型转换而已。

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

//delegate ToutMyFunc<in Tin, out Tout>(Tin obj); MyFunc<string, string> str5 = t => "农码一生"; MyFunc<object, object> str6 = t => "农码一生"; MyFunc<string, object> str7 = t => "农码一生";

泛型接口的可变性

接着看框架默认接口:

协变:(子类->父类)

IEnumerable<string> list = new List<string>(); IEnumerable<object> list2 = list;

逆变:(父类-> 子类)

IComparable<object> list3 = null; IComparable<string> list4 = list3;

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

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

// 人 public class People { } //老师(继承People[人]) public class Teacher : People { } //运动 public interface IMotion<T> { } //跑步 public class Run<T> : IMotion<T> { }

然后我们测试协变性:

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

//运动 public interface IMotion<out T>{}
IMotion<Teacher> x = new Run<Teacher>(); IMotion<People> y = x;

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

//运动 public interface IMotion<in T>{}
IMotion<People> x2 = new Run<People>(); IMotion<Teacher> y2 = x2;

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

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

IMotion<Teacher> y3 = new Run<People>();
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 2:45:02

实战测评:PixsoAI与FigmaAI生成UI界面的优缺点与差异化

里&#xff0c;AI到底能不能帮我们提速&#xff1f; 正好最近项目不忙&#xff0c;我抽出时间上手跑了几个典型需求。这篇就主要聊聊&#xff0c;实操下来AI生成UI到底能做到什么程度&#xff1f;PixsoAI和Figma MakeDesign&#xff0c;到底有什么相同点和差异化。实测一&#…

作者头像 李华
网站建设 2026/7/2 2:44:26

堆排序实现教程

堆排序算法详解&#xff1a;从原理到实现一、什么是堆排序&#xff1f;堆排序&#xff08;Heap Sort&#xff09;是一种基于二叉堆数据结构的比较排序算法&#xff0c;由J.W.J. Williams于1964年发明。它结合了插入排序和归并排序的优点&#xff0c;具有原地排序&#xff08;只…

作者头像 李华
网站建设 2026/7/2 2:42:53

AI虚拟团队自动化进化:从“人盯人“到“自愈系统“

&#x1f4a1; 一句话总结&#xff1a;前三篇讲了架构、踩坑、通信。这篇讲实战进化——系统怎么从"人工调度"一步步变成能自动发现问题、自动修复、自动重试的"自愈系统"。核心是一个字&#xff1a;闭环。&#x1f4cc; 前情回顾 第一篇&#xff1a;我用4…

作者头像 李华
网站建设 2026/7/2 2:40:50

[PHP内核探索]PHP中的哈希表

HashTable的介绍 哈希表是实现字典操作的一种有效数据结构。 定义 简单地说&#xff0c;HashTable(哈希表)就是一种键值对的数据结构。支持插入&#xff0c;查找&#xff0c;删除等操作。在一些合理的假设下&#xff0c;在哈希表中的所有操作的时间复杂度是O(1)(对相关证明感…

作者头像 李华
网站建设 2026/7/2 2:40:01

[Arccosine节点]原理解析与实际应用

函数是余弦函数的反函数&#xff0c;它能够将余弦值映射回对应的角度值。在Shader Graph中&#xff0c;Arccosine节点的实现基于标准的数学库函数&#xff0c;确保了计算的准确性和效率。理解并熟练运用这个节点&#xff0c;对于创建复杂的视觉效果和实现精确的数学计算至关重要…

作者头像 李华