zoukankan      html  css  js  c++  java
  • C#4.0泛型的协变,逆变深入剖析

    C#4.0泛型的协变,逆变深入剖析

      C#4.0中有一个新特性:协变与逆变。可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的。

         协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变。什么?泛型的参数还能声明?对,如果有了参数的声明,则该泛型接口或者委托称为“变体”。

    List<汽车> 一群汽车 = new List<汽车>();
    List<车子> 一群车子 = 一群汽车;

         显然,上面那段代码是会报错的, 虽然汽车继承于车子,可以隐士转换为车子,但是List<汽车>并不继承于List<车子>,所以上面的转换,是行不通的。

    IEnumerable<汽车> 一群汽车 = new List<汽车>();
    IEnumerable<车子> 一群车子 = 一群汽车;

    然而这样却是可以的。那么IEnumerable接口有什么不同呢,我们且看编译器的提示:

    我们可以看到,泛型参数的,用了一个“out”关键字作为声明。看来,关键是这个在起作用了。

     “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。 

     “逆变”则是指能够使用派生程度更小的类型。逆变,逆于常规的变。

    协变和逆变,使用“out”,和“in”两个关键字。但是只能用在接口和委托上面,对泛型的类型进行声明

    当声明为“out”时,代表它是用来返回的,只能作为结果返回,中途不能更改。

    当声明为"in"时,代表它是用来输入的,只能作为参数输入,不能被返回。

    回到上面的例子,正因为“IEnumerable”接口声明了out,所以,代表参数T只能被返回,中途不会被修改,所以,IEnumerable<车子> 一群车子 = 一群汽车;  这样的强制转换

    是合法的,IL中实际上是作了强制转换的。

     IEnumerable是NET中自带的,其余还有如下接口和委托:

    复制代码
    接口:           
    IQueryable<out T> IEnumerator<out T> IGrouping<out TKey,out TElement> IComparer<in T> IEqualityComparer<in T> IComparable<in T> 委托:
    System.Action
    <in T> System.Func<Out Tresult> Predicate<in T> Comparison<in T> Converter<in TInput,out TOutput>
    复制代码

    此外,我们自己定义泛型接口的时候也可以使用协变和逆变,我们不妨来看一个示例,来体现协变的特征

        interface 接口<out T>
        {
            T 属性 { get; set; }
        }

    我定义一个接口,一个具有get和set访问器的属性,然而,编译是报错的,提示:变体无效: 类型参数“T”必须为对于“test.接口<T>.属性”有效的 固定式。“T”为 协变。

    正因为我声明了T为协变,所以,T只能被返回,不允许被修改,所以,如果去掉“set”访问器,才可以编译通过。

    同样,如果我在“接口”中声明一个方法

    void 方法(T t);

    同样是会报错的,T被声明了协变,“方法(T t)”的存在就不可取。

    复制代码
    class Program
        {
            static void Main(string[] args)
            {
                接口<汽车> 一群汽车 = new 类<汽车>();
                接口<车子> 一群车子 = 一群汽车;
            }
        }
        interface 接口<out T>
        {
            T 属性
            {
                get;
            }
        }
        class 类<T> : 接口<T>
        {
            public T 属性
            {
                get { return default(T); }
            }
        }
    复制代码

    上面的代码是可以编译通过的,因为泛型接口“接口”声明了协变,所以“接口<车子> 一群车子 = 一群汽车;”是可以强制转换成功的,看吧,我们自己声明的同样可以实现目的。

     如果我把以上的代码,把“out”改成“in”呢? 显然不行,因为声明“in”规定了T不能被返回,编译无法通过的。

    然而下面的代码是正确的:

    复制代码
        interface 接口<in T>
        {
            void 方法(T t);
        }
        class 类<T> : 接口<T>
        {
            public void 方法(T t)
            {
               
            }
        }
    复制代码

    声明“in”不允许被返回,但是可以进行更改。

    接着看:

    复制代码
            static void Main(string[] args)
            {
                接口<车子> 一群车子 = new 类<车子>();
                接口<汽车> 一群汽车 = 一群车子;
            }
    复制代码

    啊,这怎么也可以啊,“车子”是父类,“汽车”是子类,汽车转换为车子正常,车子转换为汽车,这样也行?

    其实“车子”也好,“汽车”也好,在这里都只是泛型参数,并不是他们俩之间的转换,这个基础的概念必须明白,别绕进去了。
    这就是逆变。因为“接口”声明了“in”关键字,声明为逆变,让参数去接受一个相对更“弱“的类型,其实是让一个参数的类型,更加具体化,更明确化的一个过程。

      

     
     
     
  • 相关阅读:
    HTTP请求方法 GET POST【总结】
    Data Binding MVVM 数据绑定 总结
    Butter Knife 黄油刀
    原生JS强大DOM选择器querySelector与querySelectorAll
    JS日期Date详解与实例扩展
    JS中的冒泡排序以及实现一个数组中得最到最大的数字小例
    jquery.roundabout.js实现3D图片层叠旋转木马切换
    AngularJS 表格
    AngularJS XMLHttpRequest
    AngularJS Filters
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3525534.html
Copyright © 2011-2022 走看看