Covariant and Contravariant Types
Covariant and Contravariant Types
About
- Covariance: Preserves inheritance (return types)
- Contravariance: Reverses inheritance (parameters)
- Dart 2.x: Sound type system
- Use Cases: Flexible yet type-safe APIs
Main Topics
-
Return Type Covariance
-
Definition: Subclass narrows return type
-
Example:
abstract class Animal { Animal clone(); } class Cat extends Animal { @override Cat clone() => Cat(); // More specific }
-
-
Parameter Types
-
Definition: Normally invariant
-
Example:
class Printer { void print(Animal a) {...} } class CatPrinter extends Printer { // Can't override with void print(Cat c) }
-
-
covariant Keyword
-
Definition: Explicit override
-
Example:
class AnimalShelter { void adopt(covariant Animal a) {} } class CatShelter extends AnimalShelter { @override void adopt(Cat c) {} // Allowed }
-
-
Generic Variance
- Definition: Collection types
- Example:
List<Cat> cats = <Cat>[]; List<Animal> animals = cats; // Error in Dart 2
-
Type Safety
- Definition: Runtime checks
- Example:
void execute(void Function(Animal) op) { op(Cat()); // Safe }
How to Use
- Return Types: Narrow when possible
- Parameters: Use covariant carefully
- Generics: Understand invariance
- Safety: Prefer sound code
How It Works
- Compilation: Static checks
- Runtime: Additional checks
- Soundness: Dart 2 guarantee
- Performance: Minimal overhead
Example:
abstract class AnimalHandler {
void handle(covariant Animal a);
}
class CatHandler extends AnimalHandler {
@override
void handle(Cat c) { // Allowed due to covariant
print('Handling cat');
}
}
void main() {
AnimalHandler handler = CatHandler();
handler.handle(Cat()); // Valid
// handler.handle(Dog()); // Runtime error
}
Conclusion
Dart’s type system carefully balances flexibility and safety through covariance in return types and explicit covariant parameter overrides. Understanding these variance concepts helps design APIs that are both type-safe and convenient to use while leveraging polymorphism.