zoukankan      html  css  js  c++  java
  • 协变(covariant)和逆变(contravariant)

    我们知道子类转换到父类,在C#中是能够隐式转换的。这种子类到父类的转换就是协变

    而另外一种类似于父类转向子类的变换,可以简单的理解为“逆变”。

    上面对逆变的简单理解有些牵强,因为协变和逆变只能针对接口和代理类型。而父类和子类之间不存在这种逆变的概念。

    协变和逆变的本质都是子类安全的转到父类的过程。

    下面就来加深下印象,先定义两个类Car和Baoma

        public class Car
        {
        }
    
        public class Baoma : Car
        {
        }

    明显Baoma(宝马)是Car的子类

    1,先来看看协变

    协变在C#中要用out关键字标明,用这个关键字就表示参数T只能用于函数,属性等的返回值。

        //协变(covariant)
        public interface ICo<out T>
        {
            T Test1();
        }
                //协变(covariant)
                ICo<Car> ico1 = null;
                ICo<Baoma> ico2 = null;
                ico1 = ico2;//子类-->父类
                Car car = ico1.Test1();//实际调用ico2.Test1()返回Baoma类型,当然可以赋值给父类Car
                //ico2 = ico1;//error
                //Baoma baoma = ico2.Test1();//实际调用ico1.Test1()返回Car类型,赋值给子类Baoma,显然不能保证类型安全

    2,在来看看逆变

    逆变在C#中用in关键字标明,表明参数T只能用于函数的参数。

        //逆变(contravariant)
        public interface IContra<in T>
        {
            void Test1(T t);
        }
                //逆变(contravariant)
                IContra<Car> icontra1 = null;
                IContra<Baoma> icontral2 = null;
                //icontra1 = icontral2;//error
                //icontra1.Test(new Car()); 实际调用IContra<Baoma>.Test(Baoma),参数Car不能安全的转换为Baoma
                icontral2 = icontra1;//看似很奇怪,但这里不是Car->Baoma,而是IContra<Car>->IContra<Baoma>
                icontral2.Test1(new Baoma());//实际调用IContra<Car>.Test(Car),参数Baoma能安全的转换为Car

    上面的代码中已经标注详细了调用的过程,对于理解很有帮助。下面分析两种复杂一点的过程。

    3,带有逆变或协变的接口作为函数参数

        public interface IFoo<in T>
        {
            void Test(T t);
        }
        public interface IBar<out T>//这里T必须用out,而不是in
        {
            void Test(IFoo<T> foo);
        }
                IFoo<Car> ifoo1 = null;
                IBar<Car> ibar1 = null;
                IBar<Baoma> ibar2 = null;
                ibar1 = ibar2;//协变
                ibar1.Test(ifoo1);//实际调用IBar<Baoma>的Test(IFoo<Baoma>),
                //要求IFoo<Car>要转换到IFoo<Baoma>,这就需要IFoo对T逆变,即用in关键字

    引用装配脑袋对这种情况的归纳:

    //如果一个接口需要对T协变,那么这个接口所有方法的参数类型必须支持对T的反变。
    //同理我们也可以看出,如果接口要支持对T反变,那么接口中方法的参数类型都必须支持对T协变才行。
    //这就是方法参数的协变-反变互换原则。

    4,带有逆变或协变的接口作为函数的返回值

        public interface IFoo<in T>
        {
            void Test(T t);
        }
        public interface IBar2<in T>
        {
            IFoo<T> Test();
        }
                IBar2<Car> a = null;
                IBar2<Baoma> b = null;
                b = a;//逆变
                IFoo<Baoma> ibaoma = b.Test();//实际调用IBar2<Car>.Test返回IFoo<Car>类型,IFoo<Car>要转换成IFoo<Baoma>
                                              //需要IFoo对T逆变,这种函数返回值的转换方向是一致的。

    引用装配脑袋对这种情况的归纳:

    //如果一个接口需要对T进行协变或反变,那么这个接口所有方法的返回值类型必须支持对T同样方向的协变或反变。
    //这就是方法返回值的协变-反变一致原则。

    以上仅仅是个人理解归纳,关于这个概念的理解,可以参看园子里的这两篇文章,写的非常详细。

    装配脑袋的:

    http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html

    还有这篇,用图的方式很好理解:

    http://www.cnblogs.com/lemontea/archive/2013/02/17/2915065.html

      

  • 相关阅读:
    51nod乘积之和
    Dell服务器安装OpenManage(OMSA)
    Nginx反向代理PHP
    搭建haproxy
    108. Convert Sorted Array to Binary Search Tree
    60. Permutation Sequence
    142. Linked List Cycle II
    129. Sum Root to Leaf Numbers
    118. Pascal's Triangle
    26. Remove Duplicates from Sorted Array
  • 原文地址:https://www.cnblogs.com/xiashengwang/p/3528541.html
Copyright © 2011-2022 走看看