关键字out和in相信大家都不陌生,系统定义的很多泛型类型大家F12都或多或少看见了。但是实际中又很少会用到,以前在红皮书里看到,两三页就介绍完了。有的概念感觉直接搬出来的,只是说这样写会怎样,并没有形象的将为什么这么设计,什么时候有用。再加上是翻译的语义很生硬,理解起来很费劲。自然又百度一通,看了一大堆大家各抒己见,这东西还是像一个低分辨率的图片一样,不够清晰。其实现在各种知识点基本都知道大概是怎么回事,怎么用,但是总感觉少点什么,不够高清。于是最近写了个控制台,把各种不够高清或者需要高清显示的知识点捋了一边。果然纸上得来终觉浅,欲知此事写代码。好多东西尝试一遍或者配合使用,的确感觉神清气爽,很多设计知道了缘由,世界都高清了。废话不多说,分享下自认为简单粗暴有效的愚见吧。

------------------------------------------------关于定义的验证-----------------------------------------------------------------------

  既然是泛型out in关键字(非泛型试过了),那就写一个吧。于是

  大意就是只能用于接口和委托的泛型,于是这样写

  果然OK,为什么只能接口和委托,一时半会想不通,先不管,再捋捋。红皮书里写了一堆,不理解没关系,但是说了一个关键点,in修饰的泛型只能是传入参数,out修饰的只能是返回类型。问题又来了,为什么这么设计,想不通,先不管,验证下

  果然,接着捋。改正错误,现在一个泛型接口带in,out关键字的定义好了。接口能干什么,就是实现它啊。实现泛型接口需要指定泛型为具体类型,红皮书里讲了一堆,in,out反正是跟类型转换有关,那这里的in,out肯定是要作用于父类子类才有效果。于是定义一个父类和子类,再实现接口大概就是这样。

  实现时in out的类型可以随意指定,只要满足in是入参,out是返回类型就行了。既然能这样写,那就这样吧。红皮书里写到用了in之后实现接口的类能怎么怎么转换,用out之后能怎么怎么转换,反正一大堆,很绕,重点就是实现后,父类子类分别作为泛型类型之间能倒腾了。管他的,我们自己来倒腾下吧。

-----------------------------------------------------------------关于转换的验证-------------------------------------------------------

  1.现在GT_ClassC 继承自 GT_InterfaceC<GT_Child, GT_Parent>,于是这样写是OK的。

  既然是父类子类对泛型不同实现的转换,那我们就修改左边的父类或子类泛型,看看能不能转换。首先把左边的子类改为父类,于是是两个父类实现的泛型接口。

  结果是不能自动转换,为什么左边的参数从子类变成父类,就报错了。

  来捋一捋,首先左边是in修饰,所以左边的泛型肯定只能是入参类型。我们看看GT_ClassC类Show方法的入参是什么,是子类GT_Child对吧,假设方法体里已经调用了子类的相关成员,这时候我们把入参类型改成他的父类,会有什么问题。子类的成员父类不一定有,这就是关键,方法体有异常风险。所以这里的入参类型的只能兼容GT_Child的同类或子类。

  是不是呼之欲出,in修饰的类型在泛型接口实现后相互转换时,左边的该类型只能是右边该类型的同类或子类。

  再看红皮书里对in抗变的描述,大意父类到子类的转换(这样写谁明白),其实意思应该是:in修饰的类型只能向下兼容,编译器允许转换右边的类型被转换成子类。

  2.那再来看看out吧,out是GT_InterfaceC第二个类型的修饰符,所以我们在刚才不报错的写法上修改下第二个参数,于是

  果然,报错了。那就捋一捋吧。

  out关键字修饰的类型只能是返回类型,看看GT_ClassC中Show方法的返回类型是什么,是GT_Parent对吧。为什么把左边的out类型从父类改为子类就报错了呢。结合上面的理解,因为该类型肯定是返回类型,假设返回的是父类,现在要把父类转换成子类会怎样。看看实验结果

  父类到子类的转换不能隐式转换,因为父类可以有多个子类,转换需要强制转换。所以Show方法的返回类型只能隐式转换成父类。

  是不是呼之欲出:out修饰的类型在泛型接口实现后相互转换时,左边的该类型只能是右边该类型的同类或父类。简单来说out修饰的类型只能向上兼容。和in真的是前呼后应啊。再回头一想,要是in和out不限制:in只能修饰入参类型,out只能修饰返回类型,是不是上面的理论都不能成立,现在知道为什么有这样的约束了吧。

------------------------------------------------------------分析-----------------------------------------------------------------------------------

  下面贴上写代码的时候的一点总结帮助理解

  其实刚开始GT_ClassC实现接口的类型,in out对应的类型是和现在图中颠倒的,这样一来,应该会有人想到了,不管左边是什么类型,右边都能转换成功。所以刚开始我把左边子类父类不管怎么替换都没问题,我以为只要用了in out约束就可以任意写了,但是机智的我立马觉得此事必有蹊跷。要是没问题,还讲什么协变抗变,直接说入参in约束,返回类型out约束就行了。于是有了以上推论。

  看到这是不是觉得该完了。too young, simple is good.

  3.再回头看最开始的疑问,为什么只能是泛型接口或泛型委托可以使用in out。我们写两行代码看看

  定义一个简单泛型,发现不管怎么转换都不行。

  泛型转换是基于接口,先不管具体实现类型是什么,两个同一泛型接口的成员当然可以转换。而具体的类型转换就要基于in和out的约束了(没有关键字约束不管具体实现类型之间有无继承关系都无法转换,已验证)。

  再看看委托,委托可以理解成一种类型,定义(指定参数类型和返回类型的方法)的类型。是不是和泛型很像,事件就相当于委托的实现,是不是有泛型接口的影子。

  泛型委托涉及到泛型,有入参和返回类型当然可以使用in out 约束泛型达到(泛型委托实现互相转换)的编译器检查。

  下面贴上比较完整的demo方便大家整体查看,好些报错的验证性代码删了,有兴趣大家可以自己验证一下。

让我们用心感受泛型接口的协变和抗变out和in的更多相关文章

  1. C#中协变与抗变(逆变)

    泛型在.NET 2.0中正式的引入.在使用泛型的过程中,联系上面向对象的继承性.往往很容易想当然敲出类似以下代码 List<Animal> animalLst=new List<Do ...

  2. c# 协变与抗变

    定义 协变:与原始类型转换方向相同的可变性称为协变. 抗变:与派生类型转换方向相同的可变性称为抗变. 补充: 参数是协变的,可以使用派生类对象传入需要基类参数的方法,反之不行 返回值是抗变的,不能使用 ...

  3. C#中泛型方法与泛型接口 C#泛型接口 List<IAll> arssr = new List<IAll>(); interface IPerson<T> c# List<接口>小技巧 泛型接口协变逆变的几个问题

    http://blog.csdn.net/aladdinty/article/details/3486532 using System; using System.Collections.Generi ...

  4. .NET 4.0中的泛型的协变和逆变

    转自:http://www.cnblogs.com/jingzhongliumei/archive/2012/07/02/2573149.html 先做点准备工作,定义两个类:Animal类和其子类D ...

  5. .NET 4.0中的泛型协变和反变

    转自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html 随Visual Studio 2010 CTP ...

  6. .NET 4.0中的泛型协变和逆变

    随Visual Studio 2010 CTP亮相的C#4和VB10,虽然在支持语言新特性方面走了相当不一样的两条路:C#着重增加后期绑定和与动态语言相容的若干特性,VB10着重简化语言和提高抽象能力 ...

  7. 转载.NET 4.0中的泛型的协变和逆变

    先做点准备工作,定义两个类:Animal类和其子类Dog类,一个泛型接口IMyInterface<T>, 他们的定义如下:   public class Animal { } public ...

  8. 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4

    前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...

  9. C#泛型中的抗变和协变

    在.net4之前,泛型接口是不变的..net4通过协变和抗变为泛型接口和泛型委托添加了一个重要的拓展 1.抗变:如果泛型类型用out关键字标注,泛型接口就是协变的.这也意味着返回类型只能是T. 实例: ...

随机推荐

  1. 高性能JavaScript--数据存储(简要学习笔记二)

    1.JavaScript中四种基本数据存取位置:字面量,本地变量,数组元素,对象成员. 一般来说:[字面量,局部变量]运行速度>[数组,对象成员]   2.内部属性包含了一个函数被创建的作用域中 ...

  2. PowerDesigner(数据建模)使用大全

    什么是PowerDesigner 引入百度百科的说法是: power designer是能进行数据库设计的强大的软件,是一款开发人员常用的数据库建模工具.使用它可以分别从概念数据模型(Conceptu ...

  3. 爬虫技术 -- 进阶学习(十)网易新闻页面信息抓取(htmlagilitypack搭配scrapysharp)

    最近在弄网页爬虫这方面的,上网看到关于htmlagilitypack搭配scrapysharp的文章,于是决定试一试~ 于是到https://www.nuget.org/packages/Scrapy ...

  4. android 在 ListView 的 item 中插入 GridView 仿微信朋友圈图片显示。

    转载请声明出处(http://www.cnblogs.com/linguanh/) 先上张效果图: 1,思路简述 这个肯定是要重写 baseAdapter的了,这里我分了两个数据适配器,一个是自定义的 ...

  5. 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之缓存融合技术和主要后台进程(四)

    缓存融合技术和主要后台进程(四) 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习的汇总.然后形成体 ...

  6. ASP.NET Core 中文文档 第三章 原理(12)托管

    原文:Hosting 作者:Steve Smith 翻译:娄宇(Lyrics) 校对:何镇汐.许登洋(Seay) 为了运行 ASP.NET Core 应用程序,你需要使用 WebHostBuilder ...

  7. Centos 上 Tengine安装

    安装步骤: 1.系统环境 1.1 更新系统 [root@centos ~]# yum update -y 1.2 查看环境 [root@centos ~]# cat /etc/redhat-relea ...

  8. 深入学习jQuery特性操作

    × 目录 [1]获取特性 [2]设置特性 [3]删除特性 前面的话 每个元素都有一个或者多个特性,这些特性的用途就是给出相应元素或者其内容的附加信息.操作特性的DOM方法主要有3个:getAttrib ...

  9. MessageBox.Show()的各种用法

    [函数] <整型> MessageBox(<字符串> Text, <字符串> Title, <整型> nType,MessageBoxIcon); [函 ...

  10. 【译】Core Java Questions and Answers【1-33】

    前言 译文链接:http://www.journaldev.com/2366/core-java-interview-questions-and-answers Java 8有哪些重要的特性 Java ...