你的位置:首页 > ASP.net教程

[ASP.net教程]让我们用心感受泛型接口的协变和抗变out和in


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

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

 

 

 

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

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

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

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

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

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

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

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

  是不是呼之欲出,in修饰的类型在泛型接口实现后相互转换时,左边的该类型只能是右边该类型的同类或子类。再看红皮书里对in抗变的描述,大意父类到子类的转换(这样写谁明白),其实意思应该是:in修饰的类型只能向下兼容,编译器允许转换成子类。

  那再来看看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.

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

定义一个简单泛型,发现不管怎么转换都不行。泛型转换应该是基于接口,后续推论大家自由展开。要吃饭去了,我先发表了。