C#高级语法之泛型、泛型约束,类型安全、逆变和协变-思想原理

前言:

最近一段时间再写微信小程序,导致更新的频率也不是特别的快,打算过段时间给开源出来。今天偶然间看到泛型的约束,类型安全,感觉Edison.Ma 大佬写的挺好的,就复习复习。.

一、为什么使用泛型?

泛型其实就是一个不确定的类型,可以用在类和方法上,泛型在声明期间没有明确的定义类型,编译完成之后会生成一个占位符,只有在调用者调用时,传入指定的类型,才会用确切的类型将占位符替换掉。

首先我们要明白,泛型是泛型,集合是集合,泛型集合就是带泛型的集合。下面我们来模仿这List集合看一下下面这个例子:

我们的目的是要写一个可以存放任何动物的集合,首先抽象出一个动物类:

//动物类
public class Animal
{
    //随便定义出一个属性和方法
    public String SkinColor { get; set; }//皮肤颜色
    //会跑的方法
    public virtual void CanRun()
    {
        Console.WriteLine("Animal Run Can");
    }
}

然后创建Dog类和Pig类

//动物子类 Dog
public class Dog : Animal
{
    //重写父类方法
    public override void CanRun()
    {
        Console.WriteLine("Dog Can Run");
    }
}

//动物子类 Pig
public class Pig : Animal
{
    //重写父类方法
    public override void CanRun()
    {
        Console.WriteLine("Pig Can Run");
    }
}

因为我们的目的是存放所有的动物,然后我们来写一个AnimalHouse用来存放所有动物:

//存放所有动物
public class AnimalHouse
{
    //由于自己写线性表需要考虑很多东西,而且我们是要讲泛型的,所以内部就用List来实现
    private List<Animal> animal = new List<Animal>();

    //添加方法
    public void AddAnimal(Animal a)
    {
        animal.Add(a);
    }
    //移除方法,并返回是否成功
    public bool RemoveAnimal(Animal a)
    {
        return animal.Remove(a);
    }

}

AnimalHouse类型可以存放所有的动物,但是每次存入子类对象的时候就会进行装箱操作,每次取出的话,还要再次进行拆箱操作,会消耗额外的性能,因为所有的子类都能存放,所以拆箱的话也会很麻烦。

如果我们有方法可以做到,让调用者来决定添加什么类型(具体的类型,例如Dog、Pig),然后我们创建什么类型,是不是这些问题就不存在了?泛型就可以做到。

我们看一下泛型是如何定义的:

//用在类中
public class ClassName<CName>
{
    //用在方法中
    public void Mothed<MName>() {
        
    }

    //泛型类中具体使用CName
    //返回值为CName并且接受一个类型为CName类型的对象
    public CName GetC(CName c) {
        //default关键字的作用就是返回类型的默认值
        return default(CName);
    }
}

其中CName和MName是可变的类型(名字也是可变的),用法的话就和类型用法一样,用的时候就把它当成具体的类型来用。

了解过泛型,接下来我们使用泛型把AnimalHouse类更改一下,将所有类型Animal更改为泛型,如下:

public class AnimalHouse<T>
{
    private List<T> animal = new List<T>();

    public void AddAnimal(T a)
    {
        animal.Add(a);
     }
    
    public bool RemoveAnimal(T a)
    {
        return animal.Remove(a);
    }
}

AnimalHouse类型想要存储什么样的动物,就可以完全交由调用者来决定:

//声明存放所有Dog类型的集合
AnimalHouse<Dog> dog = new AnimalHouse<Dog>();
//声明存放所有Pig类型的集合
AnimalHouse<Pig> pig = new AnimalHouse<Pig>();

调用方法的时候,原本写的是T类型,当声明的时候传入具体的类型之后,类中所有的T都会变成具体的类型,例如Dog类型,Pig类型

C#高级语法之泛型、泛型约束,类型安全、逆变和协变-思想原理

C#高级语法之泛型、泛型约束,类型安全、逆变和协变-思想原理

这样我们的问题就解决了,当调用者传入什么类型,我们就构造什么类型的集合来存放动物。

但是还有一个问题,就是调用者也可以不传入动物,调用者可以传入一个桌子(Desk类)、电脑(Computer),但是这些都不是我们想要的。比如我们需要调用动物的CanRun方法,让动物跑一下再放入集合里(z),因为我们知道动物都是继承自Animal类,所有动物都会有CanRun方法,但是如果传入过来一个飞Desk类我们还能使用CanRun方法吗?答案是未知的,所以为了确保安全,我们需要对传入的类型进行约束。