泛型接口

定义

先来看一个简单的例子:

public class Sharp

{}

public class Rectangle:Sharp

{}

上面定义了两个简单的类,一个是图形类,一个是矩形类;他们之间有简单的继承关系,正确的写法:

Sharp sharp=new Rectangle();

就是说“子类引用可以直接转化成父类引用”,或者说Rectange类和Sharp类之间存在一种安全的隐式转换。

那问题就来了,既然Rectange类和Sharp类之间存在一种安全的隐式转换,那数组Rectange[]和Sharp[]之间是否也存在这种安全的隐式转换呢?

这就牵扯到了将原本类型上存在的类型转换映射到他们的数组类型上的能力,这种能力就称为“可变性(Variance)”。在.NET中,唯一允许可变性的类型转换就是由继承关系带来的“子类引用->父类引用”转换。也就是上面例子所满足的写法。

下例:

Sharp [] sharps=new Rectangle[3];

这是可以的,这说明Rectange[]和Sharp[]之间存在安全的隐式转换。

像这种与原始类型转换方向相同的可变性就称作协变(covariant)

下例:

Rectangle [] rectanges=new Sharp[3];

这是不行,编译不通过,即数组所对应的单一元素的父类引用不可以安全的转化为子类引用。数组也就自然不能依赖这种可变性,达到协变的目的。

所以与协变中子类引用转化为父类引用相反,将父类引用转化为子类引用的就称之为抗变。

即:一个可变性和子类到父类转换的方向一样,就称作协变;而如果和子类到父类的转换方向相反,就叫抗变!

当然可变性远远不只是针对映射到数组的能力,也有映射其它集合的能力如List<T>.

到这里,很多人就会问了,说了这么多,那到底这个协变或者抗变有什么实际利用价值呢?

其价值就在于,在.net 4.0之前可以这么写:

Sharp sharp=new Rectangle();

但是不能这么写:

IEnumerable<Sharp> sharps=newList<Rectangle>();

4.0之后就允许了,因为IEnumerable<T>被声明成如下形式:

public interface IEnumerable<out T>:IEnumerable.

数组是不支持抗变的.在.NET4.0之后,支持协变和抗变的两种类型:泛型接口和泛型委托.

先来看泛型接口中的协变和抗变

定义一个泛型接口:

public interface ICovariant<T>

{}

让上面的两个类各自继承一下该接口:

public class Sharp:ICovariant<Sharp>

{}

public class Rectangle: Sharp,ICovariant<Rectangle>

{}

编写测试代码:

static void Main(string[] args)

{

ICovariant<Sharp> isharp = new Sharp();

ICovariant<Rectangle> irect = new Rectangle();

isharp = irect;

}

发现编译不通过,因为无法将ICovariant<Rectange>隐式转化为ICovariant<Sharp>!

修改接口为:

public interface ICovariant<out T>

{ }

编译顺利通过。这里我为泛型接口的类型参数增加了一个修饰符out,它表示这个泛型接口支持对类型T的协变。

即:如果一个泛型接口IFoo<T>,IFoo<TSub>可以转换为IFoo<TParent>的话,我们称这个过程为协变,而且说“这个泛型接口支持对T的协变”。

那我如果反过来呢,考虑如下代码:

ICovariant<Sharp> isharp = new Sharp();

ICovariant<Rectangle> irect = new Rectangle();

//isharp = irect;

irect = isharp;

编译错误,原因是无法将ICovariant<Sharp>隐式转换为ICovariant<Rectangle>!

修改接口为:

public interface ICovariant<in T>

{ }

编译顺利通过,这里我将泛型接口的类型参数T修饰符修改成in,它表示这个泛型接口支持对类型参数T的抗变。

即:如果有一个泛型接口IFoo<T>,IFoo<TParent>可以转换为IFoo<TSub>的话,我们就称这个过程为抗变,而且说”这个泛型接口支持对T的抗变”.

泛型接口并不单单只有一个参数,所以我们不能简单地说一个接口支持协变还是抗变,只能说一个接口对某个具体的类型参数支持协变或抗变,如ICovariant<out T1,in T2>说明该接口对类型参数T1支持协变,对T2支持抗变。

举个例子就是:ICovariant<Rectange,Sharp>能够转化成ICovariant<Sharp,Rectange>,这里既有协变也有抗变。

以上都是接口并没有属性或方法的情形,接下来给接口添加一些方法:

public interface ICovariant<in T>

{

T Method1();

void Method2(T parm);

}

只是单纯的定义一个接口,却发现编译不通过.而且无论用in还是out都不行,原因是,我把仅有的一个类型参数T即用作了函数的返回值,也用作了函数的参数类型.

(1) 当我用out修饰时,即允许接口对类型参数T协变,也就是满足从ICovariant<Rectangle>到ICovariant<Sharp>的转换,Method1返回值Rectangle到Sharp转换没有任何问题:

ICovariant<Rectangle> irect = new Rectangle();

ICovariant<Sharp> isharp = new Sharp();

isharp = irect;

Sharp sharp=isharp.Method1();

(2) 如果使用in关键字修饰时,允许接口对类型参数T抗变,也就是满足从ICovariant<Sharp>到ICovariant<Rectange>转换:

ICovariant<Rectangle> irect = new Rectangle();

ICovariant<Sharp> isharp = new Sharp();

irect = isharp;

irect.Method2(new Rectangle());

Method2(Sharp)会去替换Method2(Rectange),所以上面的最后一句代码无论以Rectange类型还是Sharp类型为参数都没有任何问题.

综上:在没有额外机制的限制下,接口进行协变或抗变都是类型不安全的。.NET 4.0有了改进,它允许在类型参数的声明时增加一个额外的描述,以确定这个类型参数的使用范围,这个额外的描述即in,out修饰符,它们俩的用法如下:

如果一个类型参数仅仅能用于函数的返回值,那么这个类型参数就对协变相容,用out修饰。而相反,一个类型参数如果仅能用于方法参数,那么这个类型参数就对抗变相容,用in修饰。

所以可以将上面的接口拆成两个接口即可:

public interface ICovariant1<out T>

{

T Method1();

}

public interface ICovariant2<in T>

{

void Method2(T parm);

}

.net中很多接口都仅将参数用于函数返回类型或函数参数类型,如:

public interface IComparable<in T>

public interface IEnumerable<out T>:IEnumerable

几个重要的注意点:

1.仅有泛型接口和泛型委托支持对类型参数的可变性,泛型类或泛型方法是不支持的。
2.值类型不参与协变或抗变,IFoo<int>永远无法协变成IFoo<object>,不管有无声明out。因为.NET泛型,每个值类型会生成专属的封闭构造类型,与引用类型版本不兼容。

3.声明属性时要注意,可读写的属性会将类型同时用于参数和返回值。因此只有只读属性才允许使用out类型参数,只写属性能够使用in参数。

接下来将接口代码改成:

public interface ICovariant<out T>

{

T Method1();

void Method3(IContravariant<T> param);

}

public interface IContravariant<in T>

{

void Method2(T param);

}

我们需要费一些周折来理解这个问题。现在我们考虑ICovariant<Rectange>,它应该能够协变成ICovariant<Sharp>,因为Rectange是Sharp的子类。因此Method3(Rectange)也就协变成了Method3(Sharp)。当我们调用这个协变,Method3(Sharp)必须能够安全变成Method3(Rectange)才能满足原函数的需要(具体原因上面已经示例过了)。这里对Method3的参数类型要求是Sharp能够抗变成Rectange!也就是说,如果一个接口需要对类型参数T协变,那么这个接口所有方法的参数类型必须支持对类型参数T的抗变(如果T有作为某些方法的参数类型)。

同理我们也可以看出,如果接口要支持对T抗变,那么接口中方法的参数类型都必须支持对T协变才行。这就是方法参数的协变-抗变互换原则。所以,我们并不能简单地说out参数只能用于方法返回类型参数,它确实只能直接用于声明返回值类型,但是只要一个支持抗变的类型协助,out类型参数就也可以用于参数类型!(即上面的例子),换句话说,in除了直接声明方法参数类型支持抗变之外,也仅能借助支持协变的类型才能用于方法参数,仅支持对T抗变的类型作为方法参数类型也是不允许的。

既然方法类型参数协变和抗变有上面的互换影响。那么方法的返回值类型会不会有同样的问题呢?

将接口修改为:

public interface IContravariant<in T>

{

}

public interface ICovariant<out T>

{

}

public interface ITest<out T1, in T2>

{

ICovariant<T1> test1();

IContravariant<T2> test2();

}

我们看到和刚刚正好相反,如果一个接口需要对类型参数T进行协变或抗变,那么这个接口所有方法的返回值类型必须支持对T同样方向的协变或抗变(如果有某些方法的返回值是T类型)。这就是方法返回值的协变-抗变一致原则。也就是说,即使in参数也可以用于方法的返回值类型,只要借助一个可以抗变的类型作为桥梁即可。

C#编程(二十九)----------泛型接口的更多相关文章

  1. 剑指Offer(二十九):最小的K个数

    剑指Offer(二十九):最小的K个数 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/baid ...

  2. Bootstrap <基础二十九>面板(Panels)

    Bootstrap 面板(Panels).面板组件用于把 DOM 组件插入到一个盒子中.创建一个基本的面板,只需要向 <div> 元素添加 class .panel 和 class .pa ...

  3. Web 开发人员和设计师必读文章推荐【系列二十九】

    <Web 前端开发精华文章推荐>2014年第8期(总第29期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  4. WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]

    原文:WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载] 我们有两种典型的WCF调用方式:通过SvcUtil.exe(或者添加Web引用)导入发布的服务元数据生成服务代理相关的代码 ...

  5. VMwarevSphere 服务器虚拟化之二十九 桌面虚拟化之安装View副本服务器

    VMwarevSphere 服务器虚拟化之二十九  桌面虚拟化之安装View副本服务器 VMware View中高可用性可是一个必须要考虑的问题.在整个虚拟桌面环境中View Connection S ...

  6. Bootstrap入门(二十九)JS插件6:弹出框

    Bootstrap入门(二十九)JS插件6:弹出框 加入小覆盖的内容,像在iPad上,用于存放非主要信息 弹出框是依赖于工具提示插件的,那它也和工具提示是一样的,是需要初始化才能够使用的 首先我们引入 ...

  7. mysql进阶(二十九)常用函数

    mysql进阶(二十九)常用函数 一.数学函数 ABS(x) 返回x的绝对值 BIN(x) 返回x的二进制(OCT返回八进制,HEX返回十六进制) CEILING(x) 返回大于x的最小整数值 EXP ...

  8. JAVA之旅(二十九)——文件递归,File结束练习,Properties,Properties存取配置文件,load,Properties的小练习

    JAVA之旅(二十九)--文件递归,File结束练习,Properties,Properties存取配置文件,load,Properties的小练习 我们继续学习File 一.文件递归 我们可以来实现 ...

  9. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】原创教程连载导读【连载完成,共二十九章】

    前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...

  10. 第三百二十九节,web爬虫讲解2—urllib库爬虫—ip代理—用户代理和ip代理结合应用

    第三百二十九节,web爬虫讲解2—urllib库爬虫—ip代理 使用IP代理 ProxyHandler()格式化IP,第一个参数,请求目标可能是http或者https,对应设置build_opener ...

随机推荐

  1. eclipse导入项目报错multiple annotations found at this line

    eclipsewindow-->preference-->Valdation-->将Manual和Build下复选框全部取消选择

  2. 2018-2019-2 《网络对抗技术》Exp0 Kali安装 Week1 20165301

    2018-2019-2 <网络对抗技术>Exp0 Kali安装 Week1 20165301 安装kali 参考此网站 设置共享文件夹 虚拟机->设置->选项->共享文件 ...

  3. MySQL ACID及四种隔离级别的解释

    以下内容出自<高性能MySQL>第三版,了解事务的ACID及四种隔离级有助于我们更好的理解事务运作. 下面举一个银行应用是解释事务必要性的一个经典例子.假如一个银行的数据库有两张表:支票表 ...

  4. OI 助手 | 简洁快速的 OI 工具箱 (原 竞赛目录生成)

    原竞赛目录生成 (4.0 版本前) 开发者:abc2237512422 OI 助手是一个轻量简洁的 OI 工具箱.你可以使用它来快速进行 OI 竞赛中一些繁琐的操作,例如生成竞赛目录.对拍.它为你省去 ...

  5. Kosaraju算法学习

    Kosaraju 算法学习 序 这星期捣鼓了一个新的算法--Kosaraju算法 今天分享给大家 简介 Kosaraju算法,其实与tarjan算法差不多.但是码量较小,容易记忆.其时间复杂度与tar ...

  6. hdu 5038 (2014北京网络赛G 排序水题)

    题意:有n个数字,带入10000 - (100 - ai) ^ 2公式得到n个数,输出n个数中频率最大的数,如果有并列就按值从小到大都输出输出,如果频率相同的数字是全部的n个数,就输出Bad....题 ...

  7. kgtemp文件转mp3工具

    kgtemp文件是酷我音乐软件的缓存文件,本文从技术层面探讨如何解密该文件为mp3文件,并通过读取ID3信息来重命名. kgtemp解密 kgtemp文件前1024个字节是固定的包头信息,解密方案详细 ...

  8. 【BZOJ】3168: [Heoi2013]钙铁锌硒维生素

    题解 Ca Fe Zn Se 显然我们既然初始矩阵就能通过线性变换变成单位矩阵,则该矩阵一定有逆 没有逆输出NIE 而且因为这些向量两两正交,则表示一个向量的时候表示方法唯一 那么我们求一个逆可以求出 ...

  9. 牛客练习赛16 E - 求值

    题目大意: 链接:https://www.nowcoder.com/acm/contest/84/E 给定n个数字a1, a2, ..., an. 定义f(l, r) = al | al+1| ... ...

  10. 关于 facebook

    2017/10/29 Facebook账号分分钟被禁用,见怪不怪就好了,禁了就申诉呗 Facebook 如果遇到帐号被停用 / 帐号被封锁,大致上来说有叁个原因: 1, 名字用假名 2, 一个人拥有多 ...