首页 > 编程语言 >重学c#系列——逆变和协变[二十四]

重学c#系列——逆变和协变[二十四]

时间:2022-11-23 22:11:29浏览次数:48  
标签:person c# IEnumerable 逆变 协变 new public

前言

简单整理一下逆变和协变。

正文

什么是逆变和协变呢?

首先逆变和协变都是术语。

协变表示能够使用比原始指定的派生类型的派生程度更大的类型。

逆变表示能够使用比原始指定的派生类型的派生程度更小的类型。

这里student 继承 person。

这里这个报错合情合理。

这里可能有些刚入门的人认为,person 不是 student 的父类啊,为什么不可以呢?

一个列表student 同样也是一个列表的 person啊。

这可能是初学者的一个疑问。

但是实际情况是list 是一个类型, list 是一个类型。

所以他们无法隐式转换是正常的。

但是这样写就可以:

static void Main(string[] args)
{
	IEnumerable<Student> students = new List<Student>();
	IEnumerable<Person> peoples = students;
}

这样写没有报错,理论上IEnumerable是一种类型,IEnumerable是一种类型,不应该能隐私转换啊。

为什么呢?因为支持协变。

协变表示能够使用比原始指定的派生类型的派生程度更大的类型。

他们的结构如上。因为student是person的派生类,IEnumerable的派生程度比IEnumerable大。

协变怎么声明呢:

public interface IEnumerable<out T> : IEnumerable
{
	//
	// 摘要:
	//     Returns an enumerator that iterates through the collection.
	//
	// 返回结果:
	//     An enumerator that can be used to iterate through the collection.
	new IEnumerator<T> GetEnumerator();
}

这里协变有个特点,那就是协变参数T,只能用于返回类型。

原因是在运行时候还是new List(),返回自然是Student,那么student 可以赋值给person,这没问题。

那么协变参数T,不能用于参数呢? 是这样的。

比如 IEnumerable里面有一个方法是:

public void test(T a)
{
    
}

在IEnumerable 中原本要传入一个Student,现在使用了IEnumerable,那么就可以传入person。

person 要转换成student,显然是不符合的。

那么协变是这样的,那么逆变呢?

public interface ITest<in T>
{
	public void Run(T obj);
}

public class Test<T> : ITest<T>
{
	public void Run(T obj)
	{
		throw new NotImplementedException();
	}
}

然后这样使用:

static void Main(string[] args)
{
	ITest<Person> students = new Test<Person>();
	ITest<Student> peoples = students;
	peoples.Run(new Student());
}

这里的逆变只能作用于参数。

先说一下为什么能够作用于参数,就是在运行的时候本质还是new Test(),要传递的是一个person,如果传递一个student,那么也是可以的。

然后为什么不能作用于返回值呢?

假如ITest 可以这样:

public interface ITest<in T>
{
	public T Run()
	{ 
	}
}

在运行时候是Test(),那么调用run返回的是person,但是赋值给了Student类型,和上面同样的问题哈。

所以协变不能作用于参数,逆变不能作用于返回值。

那么也就是说要摸只能协变,要摸只能逆变。

下面是委托中的逆变:

Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());

原理就是Derived继承自Base,原本需要传入base,现在传入Derived,当然也是可以的。

之所以这么设计是一个哲学问题,那就是子类可以赋值给父类,父类能办到的子类也能办到,他们分别对应的是协变和逆变。

下一节委托。

标签:person,c#,IEnumerable,逆变,协变,new,public
From: https://www.cnblogs.com/aoximin/p/16917267.html

相关文章

  • C++零基础入门学习路线图
    C++入门学习路线图分为三阶段:C++基础入门、C++核心编程、C++提高编程。以下学习路线图参考B站黑马程序员《匠心精作C++从0到1入门编程》C++基础入门 1C++初识 ......
  • CodeTON Round 3【杂题】
    C.ComplementaryXOR给定两个长度均为\(n\)的\(\tt{01}\)串\(a,b\),你可以使用下面这种操作:选择两个下标\(l,r(1\lel\ler\len)\)。对于所有的\(i\in[l,r]\),......
  • EF Core迁移失败 ,It was not possible to find any compatible framework version
    前言:其实我很早之前就知道可以这样做,但是现在遇到这个错误,我忘记了答案。所以还是记录下来吧。。。。。参考链接:https://learn.microsoft.com/en-us/answers/questio......
  • freeCodeCamp_技术文档页
    点击查看代码<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="view......
  • 恢复 IIS Express SSL证书 ,IIS Express 调试站点 ERR_CONNECTION_RESET
    前言:一波未平,另一波又起...之前写了篇“Asp.NetCore作为服务(WindowsService)部署与SSL设置整理”随笔,在操作过程中把IISExpress的证书给删掉了,然后其他程序......
  • Testbench 的编写与应用
    1.Testbench的概念Testbench是一种用任意语言编写的程序或模块,用于在模拟过程中执行和验证硬件模型的功能正确性。Verilog主要用于硬件建模(模拟),该语言包含各种资源,用于......
  • Docker学习笔记七:Docker提交镜像到阿里云仓库
    一、准备1、开启阿里云镜像仓库a.访问阿里云地址并登陆地址:http://cr.console.aliyun.comb.选择“容器镜像服务”c.使用“个人实例”构建仓库d.创建命名空......
  • C++全栈开发学习路线图
    C语言基础与提高 C语言基础 指针、内存管理 变量、条件、字符串、数组、函数、结构体 C语言提高 多级指针的使用 接口的封......
  • Oracle-重建临时表空间 rebuild temporary tablespace
    查询用户的默认表空间/临时表空间TEMPselectuserid,username,account_status,to_char(created,'yyyymmdd'),default_tablespace,tem......
  • React 组件通信总结
    React组件通信总结父子通信传递数据(父传子)与传递方法(子传父)/**@Author:[email protected]*@Date:2022-11-2116:02:17*@LastEditors:Hua......