Difference Between Covariance and Contravariance
Nimesh Ekanayake
Technical Consultant @ Platned | MSc | Lecturer | IFS Certified x2 | Boomi Certified
Covariance and contravariance are concepts in computer science that describe the relationship between two types. These concepts are often used in the context of generic types in programming languages, such as C# and Java.
Covariance allows a derived type to be used where a base type is expected. For example, if class B is derived from class A, then a covariant type can be used in place of a type of A.
Contravariance, on the other hand, allows a base type to be used where a derived type is expected. For example, if class B is derived from class A, then a contravariant type can be used in place of a type of B.
Here is an example of covariance and contravariance in C#:
class A {}
class B : A {}
// Covariance
IEnumerable<A> a = new List<A>();
IEnumerable<B> b = new List<B>();
a = b; // OK, because B is derived from A
// Contravariance
Action<B> bAction = x => {};
Action<A> aAction = bAction; // OK, because A is a base of B
In this example, the IEnumerable<T> interface is covariant, which means that it can be assigned a value of a derived type. The Action<T> delegate is contravariant, which means that it can be assigned a value of a base type.
Covariance and contravariance can be useful in situations where you need to work with a variety of types, but you want to maintain a consistent interface. For example, if you have a method that expects an IEnumerable<T> parameter, you can use covariance to allow the method to accept a variety of types, as long as they are derived from the expected type. Similarly, if you have a delegate that expects an Action<T> parameter, you can use contravariance to allow the delegate to accept a variety of types, as long as they are base types of the expected type.
Here is an example of covariance and contravariance in Java:
class A {}
class B extends A {}
// Covariance
List<A> a = new ArrayList<A>();
List<B> b = new ArrayList<B>();
a = b; // OK, because B is derived from A
// Contravariance
Consumer<B> bConsumer = x -> {};
Consumer<A> aConsumer = bConsumer; // OK, because A is a base of B
In this example, the List<T> interface is covariant, which means that it can be assigned a value of a derived type. The Consumer<T> functional interface is contravariant, which means that it can be assigned a value of a base type.
Covariance and contravariance can be useful in situations where you need to work with a variety of types, but you want to maintain a consistent interface. For example, if you have a method that expects a List<T> parameter, you can use covariance to allow the method to accept a variety of types, as long as they are derived from the expected type. Similarly, if you have a functional interface that expects a Consumer<T> parameter, you can use contravariance to allow the functional interface to accept a variety of types, as long as they are base types of the expected type.
Follow Nimesh Ekanayake ?on LinkedIn