官网:http://msdn.microsoft.com/zh-cn/library/dd799517.aspx

原文链接:http://book.51cto.com/art/201112/308570.htm

参考链接:http://www.cnblogs.com/yukaizhao/archive/2011/10/27/xiebian-nibian.html

1.3.4  泛型类型的协变(covariant)和逆变(contravariant)

在.NET 4.0之前的版本中,泛型类型是不支持协变和逆变的,但是委托类型的参数是支持协变和逆变的。什么是协变和逆变呢?在编程语言中,“协变”是指能够使用与原始指定的派生类型相比派生程度更大的类型;“逆变”则是指能够使用派生程度更小的类型。

下面的代码很好地演示了委托类型的协变。假定有一个类Animals,从其派生出一个子类Dogs,那么当定义一个委托,该委托返回Animals。用户也可以将一个返回Dogs的委托赋值给该委托,称之为协变,见代码1.4。

代码1.4  委托的协变

  1. class Program
  2. {
  3. public delegate Animals HandlerMethod();    //返回Animals的委托
  4. public static Animals FirstHandler()        //返回Animals的方法实现
  5. {
  6. Console.WriteLine("返回Animals的委托");
  7. return null;
  8. }
  9. public static Dogs Secondhandler()          //返回Dogs的方法实现
  10. {
  11. Console.WriteLine("返回Dogs的委托");
  12. return null;
  13. }
  14. static void Main(string[] args)
  15. {
  16. HandlerMethod handler1 = FirstHandler;  //标准委托
  17. HandlerMethod handler2 = Secondhandler; //委托协变
  18. }
  19. }
  20. // 定义一个Animals的类
  21. public class Animals
  22. {
  23. public string  Location { get; set; }
  24. }
  25. // 定义一个派生自Animals的Dogs类
  26. public class Dogs : Animals
  27. {
  28. public string Cry { get; set; }
  29. }

在上面的代码中,首先定义了Animals类和Dogs类,然后定义了一个名为HandlerMethod的委托,该委托返回Animals类型的 值。在Main()方法中,分别赋给一个返回Animals类型的值和一个返回Dogs类型值的方法。可以看到,由于委托的协变特性,使得本来返回一个 Animals的委托可以接受一个返回Dogs的委托。

.NET 4.0引入了in/out参数,使泛型类型的协变和逆变得以实现。比如定义一个泛型接口或者是泛型委托,可以使用out关键字,将泛型类型参数声明为协变。协变类型必须满足条件:类型仅用作接口方法的返回类型,不用作方法参数的类型。

可以使用in关键字,将泛型类型参数声明为逆变。逆变类型只能用作方法参数的类型,不能用作接口方法的返回类型。逆变类型还可用于泛型约束。下面的示例演示了如何使用in/out参数来设置泛型类型的协变和逆变。协变的使用见代码1.5。

代码1.5  泛型的协变

  1. interface ITest<out T>                  //定义一个支持协变的接口
  2. {
  3. T X { get; }                            //属性
  4. T M();                                  //返回T类型的方法
  5. }
  6. //定义一个实现接口的泛型类
  7. class TestClass<T> : ITest<T>
  8. where T : Base, new()                 //约束T要派生自Base,具有构造函数
  9. {
  10. public T X { get; set; }
  11. //实现泛型方法
  12. public T M()
  13. {
  14. return new T();
  15. }
  16. }
  17. //定义两个类
  18. class Base { }
  19. class Derived : Base { }
  20. class Program
  21. {
  22. static void Main(string[] args)
  23. {
  24. ITest<Derived> _derived =
  25. new TestClass<Derived> { X = new Derived() };                                       //使用对象初始化语法赋初值
  26. ITest<Base> _base = _derived;   //泛型协变
  27. Base x = _base.X;
  28. Base m = _base.M();
  29. }
  30. }

在上面的代码中,定义了一个泛型接口ITest,注意使用了out参数以支持协变。然后TestClass泛型类实现了接口,并且定义了泛型约束指 定T类型必须是派生自Base类的子类。可以看到在Main主窗体中,定义了一个ITest的接口,然后利用泛型的协变特性来进行泛型类型之间的变换。

与协变相反的是,逆变是将基类转换为派生类,泛型逆变有如下两条规则:

泛型参数受in关键字约束,只能用于属性设置或委托(方法)参数。

隐式转换目标的泛型参数类型必须是当前类型的“继承类”。

例如,代码1.6定义了一个接口,演示了哪些是允许协变,哪些是允许逆变的。

代码1.6  接口的逆变

  1. interface ITest<in T>
  2. {
  3. T X
  4. {
  5. get;    //获取属性不允许逆变
  6. set;    //设置属性允许逆变!
  7. }
  8. T M(T o);   //只允许方法参数,不能作用于方法返回值
  9. }

与协变相反,逆变符合多态性的规律,逆变有些令人费解,不过逆变主要是为泛型委托准备的。逆变的使用如代码1.7所示。

代码1.7  委托的逆变

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Action<Base> _base = (o) => Console.WriteLine(o);//定义一个Base基类
  6. Action<Derived> _derived = _base;       //使用协变将基类转换为派生类
  7. _derived(new Derived());                    //逆变的效果
  8. }
  9. }

以上代码中创建了一个委托,是基于Base类,但是在后面的赋值语句中,将基类赋给派生类,形成了逆变。

泛型类型的协变(covariant)和逆变的更多相关文章

  1. 协变(covariant)和逆变(contravariant)

    我们知道子类转换到父类,在C#中是能够隐式转换的.这种子类到父类的转换就是协变. 而另外一种类似于父类转向子类的变换,可以简单的理解为“逆变”. 上面对逆变的简单理解有些牵强,因为协变和逆变只能针对接 ...

  2. C#中的协变(Covariance)和逆变(Contravariance)

    摘要 ● 协变和逆变的定义是什么?给我们带来了什么便利?如何应用? ● 对于可变的泛型接口,为什么要区分成协变的和逆变的两种?只要一种不是更方便吗? ● 为什么还有不可变的泛型接口,为什么有的泛型接口 ...

  3. C#中的协变OUT和逆变

    泛型接口和泛型委托中经常使用可变性 in  逆变,out  协变 从 list<string>转到list<object> 称为协变 (string 从object 派生,那么 ...

  4. C# 协变out 、逆变 in

    需求:泛型使用多态性 备注:协变逆变只能修饰 接口和委托 简单理解: 1.使用 in 修饰后为逆变,只能用作形参使用 ,参考 public delegate void Action<in T&g ...

  5. Scala 基础(十六):泛型、类型约束-上界(Upper Bounds)/下界(lower bounds)、视图界定(View bounds)、上下文界定(Context bounds)、协变、逆变和不变

    1 泛型 1)如果我们要求函数的参数可以接受任意类型.可以使用泛型,这个类型可以代表任意的数据类型. 2)例如 List,在创建 List 时,可以传入整型.字符串.浮点数等等任意类型.那是因为 Li ...

  6. Kotlin泛型与协变及逆变原理剖析

    在上一次https://www.cnblogs.com/webor2006/p/11234941.html中学习了数据类[data class]相关的知识,这次会学习关于泛型相关的东东,其中有关于泛型 ...

  7. .NET泛型03,泛型类型的转换,协变和逆变

    协变(Convariant)和逆变(Contravariant)的出现,使数组.委托.泛型类型的隐式转换变得可能. 子类转换成基类,称之为协变:基类转换成子类,称之为逆变..NET4.0以来,支持了泛 ...

  8. 深入理解 C# 协变和逆变

    msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型. “逆变”则是指能够使用派生程度更小的类型. 解释的很正确,大致就是这样,不过不够直白. 直白的理解: “协变” ...

  9. 【温故而知新-万花筒】C# 异步编程 逆变 协变 委托 事件 事件参数 迭代 线程、多线程、线程池、后台线程

    额基本脱离了2.0 3.5的时代了.在.net 4.0+ 时代.一切都是辣么简单! 参考文档: http://www.cnblogs.com/linzheng/archive/2012/04/11/2 ...

随机推荐

  1. “Stamping” PDF Files Downloaded from SharePoint 2010

    http://blog.falchionconsulting.com/index.php/2012/03/stamping-pdf-files-downloaded-from-sharepoint-2 ...

  2. 【读书笔记】iOS-Xcode-查找特殊字符的方法

    如图所示,为搜索图框,然后,点击放大镜图标------->Insert Pattern---->即可看到特殊字符----->选择特殊字符进行插入. 参考资料:<iOS开发进阶& ...

  3. vector,arraylist, linkedlist的区别是什么

    LinkedList类 LinkedList实现了List接口,允许null元素. 此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部. Lin ...

  4. IOS之UI--小实例项目--添加商品和商品名(纯代码终结版)

    前言:这个小实例项目是完完全全以MJ视频传授的优化方案一步一个思路从零开始敲出代码的,而且每一步都有思路,都有逻辑所以然.敲代码讲究思路,我个人不建议记忆太多东西,反正我记性很差的. 小贴士:文章末尾 ...

  5. centos到底下载哪个版本?

    CentOS-7.0-1406-x86_64-DVD.iso             标准安装版,一般下载这个就可以了CentOS-7.0-1406-x86_64-NetInstall.iso    ...

  6. Effective Java 65 Don't ignore exceptions

    Principle An empty catch block defeats the purpose of exceptions, which is to force you to handle ex ...

  7. 问题解决——MFC SDI程序 CFormView中控件随窗口缩放

    从来都是做对话框程序,这次想做个SDI的程序,想着用一下带Robbin界面的office2007风格,就不用使用那些花钱的商业控件/UI库了. 如果你不想看我打的文字,可以直接拷走代码,自己声明上定义 ...

  8. (四) openwrt单个ipk编译过程

    Tags : Makefile 本周是成胖子每周一博的第五周. 更好的阅读体验,请点击这里 [TOC] 前言 前一篇博客中,我们已经知道整个openwrt的编译顺序,本文我们来探讨与开发者息息相关的单 ...

  9. 使用Ajax与服务器端通信

    Ajax这个词,不代表任何东西,它仅仅是称呼一系列促进客户端与服务器通信的技术时所用的一个术语.服务器通信时Ajax技术的核心内容,其目标就是从客户端向服务器发送信息,并接受后者的回传,以求在此过程中 ...

  10. Virtual Box上安装CentOS7

    问题1:安装完没有桌面系统(Gnome或KDE)解答:安装的时候,软件选择为最小安装",更改该选择 问题2:开机提示Initial setup of CentOS Linux 7 (core ...