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

      

  • 相关阅读:
    vue实践推荐
    angularjs实现checkbox的点击-全选功能-选中数据
    是你需要的前端编码风格吗?
    webpack--前端性能优化与Gzip原理
    基于verdaccio的npm私有仓库搭建
    使用uni-app开发微信小程序
    《JavaScript设计模式与开发实践》-- 迭代器模式
    《JavaScript设计模式与开发实践》-- 发布-订阅模式
    《JavaScript设计模式与开发实践》-- 策略模式
    《JavaScript设计模式与开发实践》-- 代理模式
  • 原文地址:https://www.cnblogs.com/xiashengwang/p/3528541.html
Copyright © 2011-2022 走看看