C# 变体(variance)

    上节讲到了泛型,这节延申一下,讲一下变体。

    变体(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)

   这是我的公众号二维码,获取最新文章,请关注此号

原文地址:https://www.cnblogs.com/charlesmvp/p/13413456.html