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

 

     C#4.0中有一个新特性:协变与逆变。可能不少人在开发过程当中不经常使用到,可是深刻的了解他们,确定是有好处的。spa

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

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

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

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

然而这样倒是能够的。那么IEnumerable接口有什么不一样呢,咱们且看编译器的提示:继承

咱们能够看到,泛型参数的,用了一个“out”关键字做为声明。看来,关键是这个在起做用了。接口

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

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

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

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

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

回到上面的例子,正由于“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”关键字,声明为逆变,让参数去接受一个相对更“弱“的类型,实际上是让一个参数的类型,更加具体化,更明确化的一个过程。

相关文章
相关标签/搜索