Typescript 中的协变和逆变

2018-10-28  本文已影响18人  一大碗豆浆

Typescript的协变和逆变和C# Scala中的类似,但是Typescript的会自动算出来接口属于协变还是逆变,C# Scala中需要显示声明in out标记接口。 在typescript需要在tsconfig中使用strictFunctionTypes参数开启逆变检查,否则就是双变(协变或者逆变)。


逆变接口

接口中泛型只作为函数类型参数
interface Animal {
    Eat(): void
}

interface Dog extends Animal{
    Bark():void
}

interface Cat extends Animal{
    Meow():void
}

interface Comparer<T> {
    compareA: (a: T, b: T) => number;
}
逆变接口的赋值类型检查
declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer; 
/*
    错误, 因为调用 animalComparer(Dog)的时候,
    dogComparer会接受到一个Animal类型的参数,这是有风险的
*/
dogComparer = animalComparer;  // 正确
例外情况
interface Comparer<T> {
    compareA(a: T, b: T): number;
}
declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer; //正确
dogComparer = animalComparer;  // 正确
/*
    因为
        compareA(a: T, b: T): number;
    和
        compareA: (a: T, b: T) => number;
    不太一样,前者认为是双变, 后者认为是逆变。这样做的相当于把方法类型的声明排除在外,目的在于为了确保带泛型的类和接口(如 Array)总体上仍然保持协变。
*/

协变接口

接口中泛型只作为函数类型返回值
interface Animal {
    Eat(): void
}

interface Dog extends Animal{
    Bark():void
}

interface Cat extends Animal{
    Meow():void
}

interface Comparer<T> {
    compareB: () => T;
}
协变接口的赋值类型检查
declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer; 
// 正确
dogComparer = animalComparer;  
/*
    错误, 因为调用 dogComparer(Dog)的时候,
    animalComparer会返回一个Animal类型的值,这是有风险的
*/

总结

协变和抗逆变的意义在于泛型类型的类型转换带来的类型安全问题。
协变类型的接口只能允许派生类泛型赋值给父类泛型I<Dog> -> I<Animal>
逆变类型的接口只能允许父类泛型赋值给派生类泛型 I<Animal> -> I<Dog>
上一篇下一篇

猜你喜欢

热点阅读