为何引入协变、逆变
我们知道一个子类对象可以赋值给一个基类对象
Animal animal = new Animal();
Animal cat = new Cat();
那如果是用在泛型里面能行嘛?
List<Animal> animalsList = new List<Animal>();//pass
List<Animal> CatsList = new List<Cat>();//error
一组猫难道不是一组动物吗?错,是因为这里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关键字来保证在泛型接口和泛型委托中,这种安全的类型转换。