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

    下面的代码很好地演示了委托类型的协变。假定有一个类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类,但是在后面的赋值语句中,将基类赋给派生类,形成了逆变。

  • 相关阅读:
    500 多个 Linux 命令文档搜索
    C++ 字符串基本操作
    串的模式匹配算法
    查看、启动、关闭防火墙
    天润融通面试
    LeetCode 169. 求众数
    【面试题】人人车一次面试总结
    一个完整的 JS 身份证校验代码
    oracle多表查询
    百度地图的Icon
  • 原文地址:https://www.cnblogs.com/xcsn/p/3154202.html
Copyright © 2020-2023  润新知