• 泛型协变与抗变(二)


    前言

      在.NET 4之前,泛型接口是不变的。.NET 4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展。协变和抗变指对参数和返回值的类型进行转换。

      我们来看下到底什么是协变什么是抗变:
      如果某个返回的类型可以由其基类替换,那么这个类型就是支持协变的
      如果某个参数类型可以由其派生类替换,那么这个类型就是支持逆变(抗变)的。

    函数的类型转换

      在理解协变与抗变之前,我们看下面这个例子:

      class Program
         {
            public static string Tmain(object o)
            {
                return "aaa";
            }
    
            static void Main(string[] args)
            {
                string a = "aaa";
                object b = Tmain(a);
            }
    
         }

      我们仔细看下这个传值和返回。注意其中发现了两次隐式转换。

        1、向函数传值的时候 参数a从string类型转换成object类型

        2、最后接收返回值的时候b由string类型转换成object类型

      我们在返回函数来看。

        1、 String Tmain(object o) 可以转换成string Tmain(string o)

        2、 String Tmain(string o) 可以转换成 object Tmain(string o)

      在这里,也就是说函数输入的时候输入类型可以从object转换成string。基类-派生类

      在函数输出时,函数的输出类型(返回类型)从string转换成object。派生类-基类。

      这里就比较接近泛型接口的协变和抗变的概念了。我们再看我们开头的概念

      如果某个返回的类型可以由其基类替换,那么这个类型就是支持协变的
      如果某个参数类型可以由其派生类替换,那么这个类型就是支持逆变(抗变)的。

     

    理解泛型接口的协变和抗变(in、out)

      我们下面来看看泛型接口的协变及抗变的例子:

      首先我们看下协变,在C#高级编程(第十一版)中指出,如果泛型类型用out关键字标注,泛型接口就是协变的。这也就意味着返回类型只能是T。

        /// <summary>
        /// 标识out,意味着返回类型只能是T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        interface Itest<out T>
        {
            T Tmain(object value);
        }
    
     
        public class Test : Itest<string>
        {
            public string Tmain(object value)
            {
                return value.ToString();
            }
     }

     

    我们调用时:

     static void Main(string[] args)
            {
                Itest<string> itest = new Test();
                Itest<object> itestObj = itest;
            }

      在这里,我们最后接收其返回值的时候,理应由string类型进行接收的,但是这里我们可以修改,由其基类object类型进行替换。也就是在某个返回类型可以由其基类替换的时候,也就是支持协变了。注意其关键点。返回类型、由基类替换派生类。

     

     

      然后我们再看看那抗变也可称为逆变。在C#高级编程中指出的概念:如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入。

      /// <summary>
        /// 标识in,意味着输入类型只能是T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        interface Itest<in T>
        {
            string Tmain(T value);
        }
    
    
        public class Test : Itest<object>
        {
            public string Tmain(object value)
            {
                return value.ToString();
            }
        }
    
     
        class Program
        {
            static void Main(string[] args)
            {
                Itest<object> itest = new Test();
                Itest<string> itestStr= itest;
            }
    }

     

      这里我们看上面这个例子,其中返回类型已经是固定的string类型了。而泛型接口中的泛型类型用来作为参数传递了。我们再看调用时,正常传入object类型的参数,,但是我们修改传入参数类型为string类型也是可以的。也就是我们在参入参数时,参数可以由其派生类替换的话,那么这个类也就是支持抗变(逆变)的。注意其中关键点。传入参数,派生类替换基类。

    总结

      其实在上述例子及其概念中,我们可以发现,泛型接口的协变及抗变,也就是将类型参数返回或者传入的情况,在这情况下进行其类型的隐式转换所遵循的规律。

        协变:(使用关键字out)返回类型可以由其基类所替代的时候,就是支持协变的。

        抗变(逆变):(使用关键字in)传入参数类型可以由其派生类所代替的时候,就是支持抗变(逆变)的。

      夫学须也,才须学也,非学无以广才,非志无以成学-------诸葛亮


                          c#基础知识详解系列

      欢迎大家扫描下方二维码,和我一起学习更多的C#知识

      

  • 相关阅读:
    以最少的循环把两个数组里面的相同结果输出来
    解决PL/SQL Developer连接数据库时出现 “ORA-12541:TNS:无监听程序”错误
    AOP中Advice执行两遍的原因
    Java注释@interface的用法
    Spring进阶教程之在ApplicationContext初始化完成后重定义Bean
    Java的注解机制——Spring自动装配的实现原理
    基数排序简单Java实现
    jQuery的选择器中的通配符
    SEO 网站页面SEO优化之页面title标题优化
    IntelliJ IDEA 中文乱码解决
  • 原文地址:https://www.cnblogs.com/hulizhong/p/11239743.html
Copyright © 2020-2023  润新知