泛型的可变性只能修饰接口或者委托的类型参数;
可变性分为协变性与逆变性,出于好记忆的角度,out协变可以理解为和谐的变化,in逆变可以理解为不和谐的变化。其格式为:
interface MyList<out T>{}
//或者
private delegate A MyDelegate<out A, in B>(B tmp);
委托的泛型可变性,在类型参数为返回值或者形参的时候,in/out可以不写,因为它们在函数申明中所做的成分(返回值/实参)就已经决定了它们是in/out了。也就是说,上面这委托申明中的in和out写与不写是一致的。
泛型可变性的意义,是规定了不同泛型实例间的继承关系(这里说继承关系或许不太妥),可以达成类似以下效果:
MyList<Animal> animalLst = new MyList<Dog>();
当然MyList作为接口是无法直接实例化的,这里只是想表达泛型可变性带给我们的功能。如果没有泛型可变性,MyList<Animal>与MyList<Dog>是完全两个不同的类型,他们各自的声明无法关联彼此的实例,但是泛型可变性带给我们了这种可能性。
以下是一则完整的委托泛型可变性实例:
private class GrandParent{}
private class Parent : GrandParent{}
private class Child : Parent{}
//以下这一句委托声明,可以想成,委托的签名的要求是
//传一个B类型(或者其子类)实参进去,返回值用A类型(或者其父类)变量来接
//参数类型是B类型,实参当然可以传递B类型的子类型的实例
//返回类型是A类型,返回值当然可以用A类型的父类型的变量来接
private delegate A MyDelegate<out A, in B>(B tmp);
//上面委托声明与以下声明是完全一致的,类型参数所做成分就决定了它们的可变性:
private delegate A MyDelegate<A, B>(B tmp);
//以下这一句委托实例的声明,就是要求:
//第一个泛型参数Parent,指明myEvent返回一个类型为Parent的对象,自然返回其子类也是OK的
//第二个泛型参数Parent,指明委托实例myEvent调用时,需要传入一个类型为Parent的对象,自然形参是其父类也是OK的
//基于以上两点,Main函数中,MyFunc赋值给myEvent委托实例是合法的。
private event MyDelegate<Parent, Parent> myEvent;
private Child MyFunc(GrandParent c)
{
return null;
}
private void Main()
{
myEvent = MyFunc;
Parent ppp= myEvent0.Invoke(new Parent());
}
泛型可变性与LSP(里氏代换原则)是有相似之处的:
里氏替换原则包含以下4层含义:
- 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
里氏代换原则的3、4两点,与具有泛型可变性的委托在结果上是一致的,至于其有没有深层次关联,请各位自己再想想吧。