重学c#系列——逆变和协变[二十四]
前言
简单整理一下逆变和协变。
正文
什么是逆变和协变呢?
首先逆变和协变都是术语。
协变表示能够使用比原始指定的派生类型的派生程度更大的类型。
逆变表示能够使用比原始指定的派生类型的派生程度更小的类型。
这里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,当然也是可以的。
之所以这么设计是一个哲学问题,那就是子类可以赋值给父类,父类能办到的子类也能办到,他们分别对应的是协变和逆变。
结
下一节委托。
重学c#系列——逆变和协变[二十四]的更多相关文章
- 重学c#系列——字典(十一)
前言 重学c#系列继续更新,简单看一下字典的源码. 看源码主要是解释一下江湖中的两个传言: 字典foreach 顺序是字典添加的顺序 字典删除元素后,字典顺序将会改变 正文 那么就从实例化开始看起,这 ...
- Java中的逆变与协变
看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...
- scala 学习: 逆变和协变
scala 逆变和协变的概念网上有很多解释, 总结一句话就是 参数是逆变的或者不变的,返回值是协变的或者不变的. 但是为什么是这样的? 协变: 当s 是A的子类, 那么func(s) 是func(A) ...
- Scala 深入浅出实战经典 第81讲:Scala中List的构造是的类型约束逆变、协变、下界详解
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-97讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...
- C#4.0新特性(3):变性 Variance(逆变与协变)
一句话总结:协变让一个粗粒度接口(或委托)可以接收一个更加具体的接口(或委托)作为参数(或返回值):逆变让一个接口(或委托)的参数类型(或返回值)类型更加具体化,也就是参数类型更强,更明确. 通常,协 ...
- Java中的逆变与协变(转)
看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...
- Java 逆变与协变的名词说明
最近在研究Thinking in Java的时候,感觉逆变与协变有点绕,特意整理一下,方便后人.我参考于Java中的逆变与协变,但是该作者整理的稍微有点过于概念化,我在这里简单的说一下 我对于协变于逆 ...
- C# 逆变与协变
该文章中使用了较多的 委托delegate和Lambda表达式,如果你并不熟悉这些,请查看我的文章<委托与匿名委托>.<匿名委托与Lambda表达式>以便帮你建立完整的知识体系 ...
- Java 逆变与协变
最近一直忙于学习模电.数电,搞得头晕脑胀,难得今天晚上挤出一些时间来分析一下Java中的逆变.协变.Java早于C#引入逆变.协变,两者在与C#稍有不同,Java中的逆变.协变引入早于C#,故在形式没 ...
- .NET 4.0中的泛型逆变和协变
转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...
随机推荐
- 跟羽夏学 Ghidra ——工具
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- 【设计模式】Java设计模式 - 组合模式
Java设计模式 - 组合模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自己 ...
- salesforce零基础学习(一百一十七)salesforce部署方式及适用场景
本篇参考:https://architect.salesforce.com/decision-guides/migrate-change https://developer.salesforce.co ...
- LFS(Linux From Scratch)构建过程全记录(五):交叉工具链的构建
写在前面 本文将详细讲述如何构建工具链 前置知识 在LFS-BOOK中,我们需要学习一些关于"交叉编译"的内容,详见书本 安装Binutils-2.39 我们cd到sources文 ...
- windows系统下使用bat脚本文件设置 JDK 系统环境变量
号开头的行不要写在bat文件中 # java_init.bat # 注意文件换行符是windows系统下的(CR LF),文件编码是ANSI # path变量追加这个可以拓展到tomcat,mysql ...
- Elastic:Elastic Maps 基于位置的警报 - 7.10
文章转载自:https://elasticstack.blog.csdn.net/article/details/112535618
- 使用Docker Compose部署SpringCloud项目docker-compose.yml文件示例
注意各组件之间的依赖关系 microservice-discovery-eureka: image: reg.itmuch.com/microservice-discovery-eureka port ...
- Springboot之 Mybatis 多数据源实现
简介 上篇讲解了 JPA 多数据源实现:这篇讲解一下 Mybatis 多数据源实现 .主要采用将不同数据库的 Mapper 接口分别存放到不同的 package,Spring 去扫描不同的包,注入不同 ...
- POJ1985 Cow Marathon (树的直径)
用两次dfs求出树的直径,这两次dfs可以写在一起,当然为了方便理解,这里是分开写的. 1 //两次dfs求树的重心 2 #include<cstdio> 3 #include<cs ...
- FluentValidation 验证(二):WebApi 中使用 注入服务
比如你要验证用户的时候判断一下这个用户名称在数据库是否已经存在了,这时候FluentValidation 就需要注入查询数据库 只需要注入一下就可以了 public class Login3Reque ...