上节讲到了泛型,这节延申一下,讲一下变体。
变体(variance)是协变(convariance)和抗变(也说逆变contravariance)的统称。这个概念在.net 4中引入,在.net 2.0中就可以使用,但是比较麻烦,.net 4将这一概念封装成了特性。
讲变体之前,我们先来复习一下多态性。请看如下代码:
class Animals { public virtual void Say(){} } class Cat : Animals { public override void Say() { Console.WriteLine("小猫喵喵叫"); } } class Dog : Animals { public override void Say() { Console.WriteLine("小狗汪汪汪"); } } interface IAnimals<out T> where T :Animals, new() { void InvokeSay(); } interface IAnimalsType<in T> where T : Animals { Type GetType(); } class AnimalsType<T>:IAnimalsType<T> where T : Animals { public Type GetType() { return typeof(T); } } class AnimalsAdmin<T> : IAnimals<T> where T : Animals,new() { //此处涉及到反射,不清楚反射的读者请留意后期文章 //此处只需知道调用了传入实例类的Say()方法即可 public void InvokeSay() { Type type = typeof(T); T t = new T(); MethodInfo Say = type.GetMethod("Say"); Say.Invoke(t, null); } }
有一父类Animals,Cat和Dog继承此类,根据多态性,以下代码是可行的:
Animals cat = new Cat(); Animals dog = new Dog();
有两个接口,分别由两个类继承,先分析其中一个类和接口,下面的代码是编译不过的:
IAnimals<Animals> animals; animals=new AnimalsAdmin<Dog>();//父类是IAnimals<Dog> animals= new AnimalsAdmin<Cat>();//父类是IAnimals<Cat>
以上转换,在多态性中看似是可以的,但其实这样的转换不属于多态。多态性是基于类的继承,若两个类没有继承关系,何谈多态,AnimalsAdmin<Dog>和AnimalsAdmin<Cat>的父类和IAnimals<Animals>是平行类型关系,没有继承关系。只有以下代码是可行的:
IAnimals<Animals> animals; animals=new AnimalsAdmin<Animals>();
而变体,让这样的转换变的可行。
协变:
为了建立他们之间的继承关系,接口IAnimals<T>的类型需要设置为协变,有了协变类型,AnimalsAdmin<Dog>,AnimalsAdmin<Cat>这两个类和IAnimals<Animals>就建立了继承关系(这一点比多态性稍微复杂一些)。那如何设为协变类型呢:
interface IAnimals<out T> where T :Animals, new() { void InvokeSay(); }
T前加关键字out即可。我们来看一下运行结果:
微软的API中也有很多协变的例子:例如IEnumerable泛型接口就是协变性的。
抗变:
了解了协变,那么对于抗变,小伙伴们也能猜出是什么意思,协变是向上转换,请分析另一对接口和类,看如下代码:
AnimalsAdmin<Cat> cAdmin = new AnimalsAdmin<Cat>(); Type type = cAdmin.GetAnimalType(new AnimalsType<Animals>());
没有抗变,以上代码显然是不可行的,因为cAdmin.GetAnimalType的参数需要一个AnimalsType<Cat>类型的,为了使这种转换可行,需将IAnimalsType<T>接口设置为抗变类型:
interface IAnimalsType<in T> where T :Animals { Type GetAnimalType(); }
在T前加关键字in,我们来看一下运行结果:
抗变在.net Framework中有很多用处,IComparable泛型接口就是抗变类型。
通过变体,我们在面向泛型接口编程的时候,就可以借助多态性很灵活的编码。最后注意两点:设置为协变类型的T,只能用作返回类型和属性get访问器的类型,而设置为抗变类型的T只能用作方法的参数。
本节源码文件位于:
https://github.com/Chunlei-Su/PublicDemo/tree/master/C%23/C%23Senior/Variance(%E5%8F%98%E4%BD%93)
这是我的公众号二维码,获取最新文章,请关注此号