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

简介: 官网: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之前的版本中,泛型类型是不支持协变和逆变的,但是委托类型的参数是支持协变和逆变的。

官网: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.     }  

在上面的代码中,定义了一个泛型接口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);   //只允许方法参数,不能作用于方法返回值  

与协变相反,逆变符合多态性的规律,逆变有些令人费解,不过逆变主要是为泛型委托准备的。逆变的使用如代码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.     }  

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

相关文章
|
3月前
|
安全 C#
C#进阶-协变与逆变
我们知道子类转换到父类,在C#中是能够隐式转换的。这种子类到父类的转换就是协变。而另外一种类似于父类转向子类的变换,可以简单的理解为逆变。逆变协变可以用于泛型委托和泛型接口,本篇文章我们将讲解C#里逆变和协变的使用。逆变和协变的语法第一次接触难免感到陌生,最好的学习方式就是在项目中多去使用,相信会有很多感悟。
38 0
|
8月前
|
Java
Java泛型的协变和逆变
Java泛型的协变和逆变
44 0
|
C# 开发者
C#——协变逆变
C#——协变逆变
72 0
|
Scala 开发者
协变逆变和不变 | 学习笔记
快速学习协变逆变和不变
46 0
|
Java
Java泛型——泛型方法
Java泛型——泛型方法
126 0
|
安全 Java 编译器
Java泛型的协变与逆变
ava作为一门面相对象的语言,当然是支持面相对象的三大基本特性的,反手就蹦出三个词:封装、继承、多态。
94 0
|
编译器 C#
C#中的协变和逆变
C#中的协变和逆变
91 0
|
Kotlin
kotlin 泛型-协变、逆变
在java中,假设有一个泛型接口 GenericClass , 该接口中不存在任何以 T 作为参数的方法,只是方法返回 T 类型值: 那么,在 GenericClass 为此,我们必须声明对象的类型为 GenericClass
553 0
|
SQL 开发框架 .NET