C#高级编程之泛型三(协变与逆变)

为何引入协变、逆变

我们知道一个子类对象可以赋值给一个基类对象

Animal animal = new Animal();
Animal cat = new Cat();

那如果是用在泛型里面能行嘛?

List<Animal> animalsList = new List<Animal>();//pass
List<Animal> CatsList = new List<Cat>();//error

C#高级编程之泛型三(协变与逆变)

一组猫难道不是一组动物吗?错,是因为这里List<Animal> 与List<Cat>分别是不同的类(用ILSpy.exe 即可以看到),这两个类没有继承关系。

当然此处可以采用List<Animal> CatsList = new List<Cat>().Select(c=>(Animal)c).ToList();来实现。

泛型中的类型对应的都是强类型,泛型在使用的时候,存在不和谐的地方,正是由于这个原因,出现了协变和逆变。

协变的使用

IEnumerable<Animal> catList = new List<Cat>();//协变

此处将一组Cat赋值给一组动物,符合我们正常的理解,是协调的变化。

具体看IEnumerable接口,其公开了一个T类型返回值的接口方法,通过一个Out关键字指定该类型支持协变。具体如下:

    //
// 摘要:
// 公开枚举数,该枚举数支持在指定类型的集合上进行简单迭代。
//
// 类型参数:
// T:
// 要枚举的对象的类型。
[TypeDependencyAttribute("System.SZArrayHelper")]
public interface IEnumerable<out T> : IEnumerable
{
//
// 摘要:
// 返回一个循环访问集合的枚举器。
//
// 返回结果:
// 用于循环访问集合的枚举数。
IEnumerator<T> GetEnumerator();
}

这里IEnumerable是只读的,List是可以改的。如果List<string>可以变成List<object>的话,

即List<string>=List<object>;

那我往=List<object>里面add一个int怎么办?这样就类型不安全了,所以此处IEnumerable只能是支持协变的。

逆变的使用

协变逆变的定义

定义一个泛型接口

interface IFoo<T>

{

    void Method1(T param);

    T Method2();

}

如果我们允许协变,从IFoo<TSub>到IFoo<TParent>转换,那么IFoo.Method1(TSub)就会变成IFoo.Method1(TParent)。

我们都知道TParent是不能安全转换成TSub的,所以Method1这个方法就会变得不安全。

同样,如果我们允许反变IFoo<TParent>到IFoo<TSub>,则TParent IFoo.Method2()方法就会变成TSub IFoo.Method2(),

原本返回的TParent引用未必能够转换成TSub的引用,Method2的调用将是不安全的[返回值变成了具体的子类,父类的引用没法复制转换为子类引用了,违反LSP]。

故通过加入Out,In关键字来保证在泛型接口和泛型委托中,这种安全的类型转换。

总结

上一篇:利用radio实现纯css选项卡切换


下一篇:【Java集合系列二】LinkedList解析