协变与逆变 (Covariance and Contravariance)
协变(Covariance)和逆变(Contravariance)是类型系统中的重要概念,特别是在处理泛型和函数类型时。它们描述了子类型关系如何在不同上下文中传播。理解这些概念可以帮助你编写更加灵活且类型安全的代码。
1. 协变 (Covariance)
定义:如果 B 是 A 的子类型,那么 F<B> 也是 F<A> 的子类型。这里 F 表示一个类型构造器,例如泛型或函数返回类型。
特点:
- 协变允许将更具体的类型赋值给更通用的类型。
- 常见于输出位置,如返回值类型、读取属性等。
示例:泛型中的协变
typescript
class Animal {}
class Dog extends Animal {}
// 泛型数组是协变的
let animals: Animal[] = [];
let dogs: Dog[] = [new Dog()];
animals = dogs; // OK: Dog[] 是 Animal[] 的子类型在这个例子中,Dog[] 可以赋值给 Animal[],因为 Dog 是 Animal 的子类,所以 Dog[] 也是 Animal[] 的子类型。
示例:函数返回类型的协变
typescript
function getAnimal(): Animal {
return new Animal();
}
function getDog(): Dog {
return new Dog();
}
let getAnyAnimal: () => Animal = getDog; // OK: () => Dog 是 () => Animal 的子类型在这里,getDog 函数的返回类型 Dog 是 getAnyAnimal 返回类型 Animal 的子类型,因此可以进行赋值。
2. 逆变 (Contravariance)
定义:如果 B 是 A 的子类型,那么 F<A> 是 F<B> 的子类型。这意味着参数位置的类型是逆变的。
特点:
- 逆变允许将更通用的类型赋值给更具体的类型。
- 常见于输入位置,如函数参数类型、写入属性等。
示例:函数参数类型的逆变
typescript
function acceptsAnimal(animal: Animal): void {
console.log("It's an animal");
}
function acceptsDog(dog: Dog): void {
console.log("It's a dog");
}
let acceptAnyAnimal: (animal: Animal) => void = acceptsDog; // Error: Type '(dog: Dog) => void' is not assignable to type '(animal: Animal) => void'.
let acceptAnyDog: (dog: Dog) => void = acceptsAnimal; // OK: (animal: Animal) => void 是 (dog: Dog) => void 的子类型在这个例子中:
acceptsDog不能赋值给acceptAnyAnimal,因为acceptAnyAnimal需要能够接受任何Animal类型的对象,而acceptsDog只能接受Dog类型的对象。acceptAnyDog可以赋值给acceptsAnimal,因为acceptsAnimal接受的是更通用的Animal类型,它可以处理Dog类型的对象。
3. 不变 (Invariance)
有些情况下,类型既不是协变也不是逆变,而是不变的。这意味着即使 B 是 A 的子类型,F<B> 也不是 F<A> 的子类型,反之亦然。
示例:双向绑定属性的不变性
typescript
class Container<T> {
value: T;
}
let containerOfAnimals: Container<Animal>;
let containerOfDogs: Container<Dog>;
containerOfAnimals = containerOfDogs; // Error: Type 'Container<Dog>' is not assignable to type 'Container<Animal>'.
containerOfDogs = containerOfAnimals; // Error: Type 'Container<Animal>' is not assignable to type 'Container<Dog>'.在这个例子中,Container<T> 的 value 属性既可以用于读取也可以用于写入,因此它既不是协变也不是逆变,而是不变的。
总结
- 协变:适用于输出位置,如返回值类型、读取属性等。如果
B是A的子类型,那么F<B>也是F<A>的子类型。 - 逆变:适用于输入位置,如函数参数类型、写入属性等。如果
B是A的子类型,那么F<A>是F<B>的子类型。 - 不变:既不是协变也不是逆变,通常出现在需要双向访问(读/写)的场景中。
理解协变与逆变的概念对于正确使用泛型和函数类型非常重要。通过合理应用这些概念,你可以编写出更加灵活且类型安全的代码。