首页 > 代码库 > c#协变与逆变心得

c#协变与逆变心得

 

在读本文之前请先阅读此文:

http://www.cnblogs.com/CLR010/p/3274310.html

对于协变与逆变,始终记住三个原则,记住这三点就一切清晰了!

1.不管协变还是逆变,最终遵循的类型变换是===》子类转换为父类,比如string=》object;即被派生类转为派生类;

2.协变修饰为out,协变时,协变修饰的类型参数只能作为返回值!不能用于输入参数,编译不过去;

3.逆变修饰符为in,逆变时,逆变修饰的类型参数只能作为输入参数!不能用于返回值,编译不过去;

/// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TN">逆变类型参数</typeparam>
    /// <typeparam name="TX">协变类型参数</typeparam>

    interface IDemo<in TN, out TX>
    {
        /// <summary>
        ///协变,out修饰类型参数Y只能用于返回值!
        /// </summary>
        /// <returns></returns>

        TX Xb();

        /// <summary>
        /// 逆变,in 修饰的类型参数T只能用于输入参数!
        /// </summary>
        /// <param name="param"></param>
        void Nb(TN param);

        /// <summary>
        /// 组合的例子
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        TX Get(TN param);
    }

    class A:IDemo<object,string>
    {
        public string Xb()
        {
            throw new NotImplementedException();
        }

        public void Nb(object param)
        {
            throw new NotImplementedException();
        }

        public string Get(object param)
        {
            throw new NotImplementedException();
        }
    }

    class B:IDemo<string,object>
    {
        public object Xb()
        {
            throw new NotImplementedException();
        }

        public void Nb(string param)
        {
            throw new NotImplementedException();
        }

        public object Get(string param)
        {
            throw new NotImplementedException();
        }
    }
    static void Main()
        {
            //参数类型完全一样,可以编译通过
            IDemo<string,object> correctDemo=new B();

            //注意correctDemo2的声明方式
            IDemo<string, object> correctDemo2= new A();
           
        //正确的方式
         object result=   correctDemo2.Get("111");


         //参数类型错误
         //correctDemo2.Get(new object());

         //无法协变和逆变
         //IDemo<object, string> wrongDemo = new B();
}

分析
1.我们通过IDemo<string, object>声明了correctDemo2,所以correctDemo2的Get方法的签名是object Get(string p);注:根据原则2、3,string为逆变参数,只能做输入参数,object为协变参数,只能做返回值

 

2.根据类A的定义,我们得出类A的Get方法的签名是string Get(object param)


3.correctDemo2的引用指向了类A的实例!所以correctDemo2执行时,实际是执行的方法其实是酱紫的string Get(object param),就是A的Get方法


4.但是我们在调用correctDemo2的Get方法时传递的参数是string类型的啊!可A的Get方法接受的是object类型的参数啊!OK我们的原则1出场(回上面瞅瞅),string=》object遵循被派生类转为派生类的原则;所以Get方法可以执行了


5.然后继续,A的Get方法执行后返回的结果是string类型!而correctDemo2实际是接受一个object类型的结果;OK我们的原则1再出场,string=》object遵循被派生转为派生类的原则;所以Get方法成功返回结果~


6.第四步就是逆变的过程,第五步就是协变的过程;

 

这是correctDemo2在调用时显示的信息,需要string类型的参数,返回object的结果;

技术分享

然后我们调试的时候用即时窗口看看correctDemo2

技术分享

原来是A类型,只不过调用时是通过接口的方法签名调用的

然后再看看correctDemo2的方法

技术分享

实际上是object的参数,string的结果;

整个的执行过程是酱紫的;

图片较宽,可以右键,在新窗口中打开

技术分享

 

 

//7.所谓协变可以理解为就是子类转换为基类,逆变理解为基类转换为子类;因为我们是把A赋值给了IDemo,貌似是既有协变又有逆变,实际上只有“协变”;如上图,类型的转化都是从子类转换为父类!因为面向对象是不允许子类声明父类的;

要实现逆变和协变必须要in和out关键字实现;

举例说明,比如   

 /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T">既是返回值类型又是输入参数类型</typeparam>
    internal interface Icommon<T>
    {
       /// <summary>
        /// 既是返回值类型又是输入参数类型
       /// </summary>
       /// <param name="t"></param>
       /// <returns></returns>
        T Geta(T t);
    }

    class C:Icommon<string>
    {
        /// <summary>
        /// 返回值是string,输入参数也是string
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public string Geta(string t)
        {
            throw new NotImplementedException();
        }
    }

    class D : Icommon<object>
    {
        /// <summary>
        /// 返回值是object,输入参数也是object
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public object Geta(object t)
        {
            throw new NotImplementedException();
        }
    }
      static void Main()
        {
           //无法编译
            Icommon<object> ic = new C();

            //无法编译
            Icommon<string> id = new D();
}

以上,ic有协变的条件,无法满足逆变;因为类型参数即使返回值又是输入参数;c的Geta输入参数和返回值都是string,而ic要求返回值和输入参数都是object,返回值string=》object的协变条件是成立的,但是输入参数object=》string是背离原则【子类转换为父类】的

         id则有逆变的条件,无法满足协变;

 

个人的理解,纯属抛砖引玉,不当的地方请指正;

c#协变与逆变心得