返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >一文带你了解C#中的协变与逆变
  • 188
分享到

一文带你了解C#中的协变与逆变

C#协变逆变C#协变C#逆变 2023-02-26 08:02:47 188人浏览 安东尼
摘要

目录协变协变接口的实现逆变里氏替换原则协变 协变概念令人费解,多半是取名或者翻译的锅,其实是很容易理解的。 比如大街上有一只狗,我说大家快看,这有一只动物!这个非常自然,虽然动物并不

协变

协变概念令人费解,多半是取名或者翻译的锅,其实是很容易理解的。

比如大街上有一只狗,我说大家快看,这有一只动物!这个非常自然,虽然动物并不严格等于狗,但不会有人觉得我说的不对,把狗变成动物就是协变,C#也支持这个:

// C#6顶级语句
Dog dog= new Dog();
Animal animal= dog;

interface Animal
{}

class Dog : Animal
{}

那么接下来,大街上有一群狗,我说有一群动物,按理说也是对的,但看样子C#不这么认为

List<Dog> dogLst = new List<Dog>();
List<Animal> aniLst = dogLst;       //飙红飙红飙红了
interface Animal {}
class Dog : Animal {}

原因其实很容易理解,毕竟在上述的代码中,写了Dog:Animal,即声明了狗是动物的子类,但是并没有写List<Animal> : List<Dog>,换言之,从来没有声明过一群狗是一群动物的子类。

但是,如果不用List,而用其父类IEnumerable,写成下面这样,就又不报错了。

List<Dog> dogLst = new List<Dog>();
IEnumerable<Animal> aniLst = dogLst;

换言之,C#承认List<Dog>是IEnumerable<Animal>的子类,个中差别,只需一览源码,就会知晓:

public interface IEnumerable<out T> : IEnumerable
public class List<T> : ..., IEnumerable<T>, ...

IEnumerable无非比List多了一个out参数,有了这个参数,就拥有了协变的功能,从而当U是T的子类时,可以支持IEnumerable<U>到IEnumerable<T>的转换。

在官方文档中,指明了具有out关键字的泛型接口包括IEnumerable<T>, IEnumerator<T>, IQueryable<T>和IGrouping<TKEy,TElement>。

协变接口的实现

协变和逆变目前只能在泛型接口和委托中使用,下面新建一个泛型接口,并使用关键字out。由于使用.net6.0的顶级语句,所以接口和类的声明放在后面。

iout<string> outStr = new Out();
IOut<object> outObj = outStr;
Console.WriteLine(outObj.getName());

interface IOut<out T>
{
    T getName();
}

class Out : IOut<string>
{
    public string getName()
    {
        return GetType().Name;
    }
}

编译运行,最后输出Out,即outObj尽管在声明的时候用的是IOut<object>,但在IOut的out修饰符的作用下,成功让IOut<object>变成了IOut<string>的父类,得以顺利调用Out中的方法。

那么接下来,如果想让getName更加完备一些,例如要求实现getName(T name)这样的功能,那么经out修饰的协变接口就无能为力了,像下面这样的写法果然被无情地飙红了

interface IOut<out T>
{
    void getName(T name);
}

逆变

VS作为宇宙顶级IDE,协变逆变十分拎得清,上述代码在飙红的同时,直接给出如下错误

变型无效: 类型参数“T”必须是在“IOut.getName(T)”上有效的 逆变式。“T”为 协变。

换言之,如果想让泛型接口可以输入泛型参数,那么需要用到逆变,具体写法如下,其中修饰符in表示逆变

IIn<object> inObj = new In();
IIn<string> inStr = inObj;
inStr.getName("in");

interface IIn<in T>
{
    void getName(T name);
}

class In : IIn<object>
{
    public void getName(object name)
    {
        Console.WriteLine(name);
    }
}

逆变和协变最大的不同,并非in和out这两个修饰符的字数,而是整个替换逻辑发生了变化,上述代码中,实际上是作为子类的string调用了通过父类object作为参数定义的函数。

里氏替换原则

在具体实现了协变与逆变之后,总觉得那里怪怪的,最怪的其实还是下面这行代码的错误

//错错错错错错错错错错错错错错错错错错错错错错错错错错
interface IOut<out T>
{
    void getName(T name);
}

而且可以想象,与之相对应的下面的逆变代码也是不对的

//错错错错错错错错错错错错错错错错错错错错错错错错错错
interface IOut<in T>
{
    T getName();
}

接下来复盘一下产生这种现象的原因,为了破除命名带来的困扰,接下来考虑泛型接口I<T>,其中有一个函数T test(T t)。现有两个特定的继承自泛型接口I<A>和I<B>的类,假设I<A>要调用I<B>中的方法,那么其流程如下

1.A I<A>.test(A t),即输入一个A类型的参数

2.将这个A类型的参数t,传入到B I<B>.test(B t)。由于I<B>要求输入B类型的参数,所以要求A可以转换为B类型。

3.B I<B>.test(B t)计算完毕,返回一个B类型的参数

4.这个B类型的参数又被返回给最初的调用者A I<A>.test,而这时I<A>的函数最终将返回一个A类型的参数,换言之,在这个步骤,要求B可以转换为A。

A能转为B,然后还得B能转为A,同时A和B还不相等,这显然是不可能的。

所以逆变和协变分别实现了第2步和第4步。

如果I<A>想要调用I<B>test(B t)中的函数,那么A类型必须可以转成B类型。正如string可以转为object一样,此即逆变,用in修饰,其作用场合为子类调用父类中的方法。

如果I<A>想要调用B I<B>test(),那么作为返回值的B类型必须可以转化为A类型,此即协变,用out修饰,正是父类调用子类的方法。

协变和逆变的统一之处在于,二者都严格遵循这子类可以转变为父类的规则,此即里氏替换。这是1987年,芭芭拉·利斯科夫提出的,她也是2008年图灵奖得主。

在协变逆变的过程中,对里氏替换的遵循主要表现在当子类方法重载父类方法时

  • 方法的输入参数要更加宽松,此即逆变(IOut<object>调用IOut<string>,object比string更宽松)
  • 方法的返回值要更加严格,此即协变(IOut<string>调用IOut<object>,string比object更严格)

以上就是一文带你了解C#中的协变与逆变的详细内容,更多关于C# 协变 逆变的资料请关注编程网其它相关文章!

--结束END--

本文标题: 一文带你了解C#中的协变与逆变

本文链接: https://lsjlt.com/news/197550.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 一文带你了解C#中的协变与逆变
    目录协变协变接口的实现逆变里氏替换原则协变 协变概念令人费解,多半是取名或者翻译的锅,其实是很容易理解的。 比如大街上有一只狗,我说大家快看,这有一只动物!这个非常自然,虽然动物并不...
    99+
    2023-02-26
    C# 协变 逆变 C# 协变 C# 逆变
  • 图文详解C#中的协变与逆变
    目录前言协变和逆变总结前言 这篇文章简单说说C#中的协变和逆变。 在C#编程中,由于存在类型之间的强制转换,很容易会出现所谓的类型可变性说法,存在协变、逆变、不变三种。 就比如前一篇...
    99+
    2024-04-02
  • C#中的协变与逆变小结
    一:什么是协变与逆变 协变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,逆变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型 只有泛型接口和泛型委托...
    99+
    2024-04-02
  • C#中协变与逆变的示例分析
    这篇文章主要介绍了C#中协变与逆变的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一:什么是协变与逆变协变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类...
    99+
    2023-06-25
  • C#中的协变与逆变怎么实现
    本篇内容介绍了“C#中的协变与逆变怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前言在C#编程中,由于存在类型之间的强制转换,很容易...
    99+
    2023-06-29
  • 怎么理解Java中的逆变与协变
    这篇文章主要介绍“怎么理解Java中的逆变与协变”,在日常操作中,相信很多人在怎么理解Java中的逆变与协变问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解Java中的逆变与协变”的疑惑有所帮助!接下来...
    99+
    2023-06-02
  • C#中的协变与逆变接口怎么实现
    今天小编给大家分享一下C#中的协变与逆变接口怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。协变协变概念令人费解,多半...
    99+
    2023-07-05
  • 一句话清晰总结C#的协变和逆变
    看到过几篇协变和逆变的文章,但是总觉得写得不够清晰,文章这东西注重要是要把自己想表达的观点表达出来,这个过程应该是把复杂的东西消化出来从而简单化,清晰化,而不是故弄玄虚,反其道而行之...
    99+
    2022-11-13
    C# 协变 逆变
  • C#泛型的逆变协变之个人理解
    一般来说, 泛型的作用就类似一个占位符, 或者说是一个参数, 可以让我们把类型像参数一样进行传递, 尽可能地复用代码。 我有个朋友, 在使用的过程中发现一个问题 IFace<o...
    99+
    2023-05-14
    C#泛型的逆变协变
  • 【JAVA】一文带你了解java的数据类型与变量
    作者主页:paper jie的博客 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文录入于《JAVASE语法系列》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将jav...
    99+
    2023-09-04
    java 开发语言
  • 带你一文了解C#中的Expression
    目录前言Expression与Expression Tree参考源码总结前言 我们书接上文,我们在了解LINQ下面有说到在本地查询IEnumerbale主要是用委托来作为传参,而解析...
    99+
    2024-04-02
  • 带你一文了解C#中的LINQ
    目录前言LINQ的根基IEnumerable和IEnumeratorLINQ的基本用法扩展方法在LINQ的应用:LINQ的流式语法LINQ的查询表达式:LINQ的查询语法LINQ的延...
    99+
    2024-04-02
  • 如何理解TypeScript中的子类型、逆变、协变
    这篇文章主要介绍“如何理解TypeScript中的子类型、逆变、协变”,在日常操作中,相信很多人在如何理解TypeScript中的子类型、逆变、协变问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方...
    99+
    2024-04-02
  • 一篇文章带你了解C++ static的作用,全局变量和局部变量的区别
    目录1.static的作用2.staic全局变量和局部变量的区别,存储位置?区别: 我们自底向上的方式进行讲解:总结1.static的作用 (1) 修饰局部变量时,只初始化一...
    99+
    2024-04-02
  • 浅谈Java中的桥接方法与泛型的逆变和协变
    目录1. 泛型的协变1.1 泛型协变的使用1.2 泛型协变存在的问题1.2.1 Java当中桥接方法的来由1.2.2 为什么泛型协变时,不允许添加元素呢1.2.3 从Java字节码的...
    99+
    2024-04-02
  • 一文带你了解C++中deque的使用
    目录1)deque的定义及基本用法2)deque的迭代器3)deque的性能4)deque的应用:滑动窗口问题1)deque的定义及基本用法 要使用deque,我们需要包含头文件,定...
    99+
    2023-05-18
    C++ deque使用 C++ deque原理 C++ deque
  • 一文带你了解C++中queue的使用
    目录一、queue的定义二、queue的使用三、queue的原理四、queue的示例一、queue的定义 queue是一个类模板。它包含在头文件中。其定义形式如下: template...
    99+
    2023-05-18
    C++ queue使用 C++ queue原理 C++ queue
  • 一篇文章带你了解C++中的异常
    目录异常抛出异常基本操作自定义的异常类栈解旋异常接口声明异常变量的生命周期异常的多态c++的标准异常库编写自己的异常类总结异常 在c语言中,对错误的处理总是两种方法: 1,使用整型的...
    99+
    2024-04-02
  • 一文带你了解C语言中的0长度数组(可变数组/柔性数组)
    目录零长度数组概念0长度数组的用途GNU Document中 变长数组的支持0长度数组的其他特征零长度数组概念 众所周知, GNU/GCC 在标准的 C/C++ 基础上做了有实用性的...
    99+
    2023-03-19
    C语言0长度数组 C语言零长度数组 C语言可变数组 C语言柔性数组
  • 一篇文章带你入门java变量与类型
    目录整型变量基本语法格式代码示例长整型变量基本语法格式代码示例双精度浮点型变量基本语法格式代码示例单精度浮点型变量基本语法格式代码示例字符类型变量基本语法格式代码示例字节类型变量基本...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作