C#

Covariance and Contravariance

A brief explanation on variance in Object-Oriented programming with examples in C#

  • #variance
  • #interface
  • #csharp

Covariance and contravariace are the terms originated from Physics to explain how the scale of the factor can affect the scale of the outcome. Covariant is indicative of the scale of outcome is directly proportional to the scale of the cause whereas contravariant is inversely proportional.

In Programming, covariance and contravariance compliments the Liskov Substitution Principle of the SOLID Principles.

Simply put, contravariance is the conversion of a derived entity to its parent entity that are higher in the hierarchy. An epitome would converting Cat to Animal (upcasting).

A simple hierarchical diagram

Covariance on the other hand, is the conversion of a higher entity to a more derived, specific ones down the hierarchy. It is like converting Animal to Cat (downcasting).

Basics

Take the Animal and Cat class as example.

class Animal {
    public void Eat() => Console.WriteLine("nom, nom");
}

class Cat : Animal {
    public void Meow() => Console.WriteLine("meow");
}

The base class Animal is used for both reference when instantiating both the Animal and Cat object.

Animal x = new Animal();
Animal y = new Cat();

Both x and y are able to invoke the Eat method. However, since the Meow method only exist in the Cat class, a Cat object that is referenced by the base Animal class cannot invoke that method.

x.Meow(); // compilation error
y.Meow(); // compilation error

Only the Cat object that has its own class as reference is able to access the Meow method.

Cat z = new Cat();
z.Meow();

Generics

When it comes to generic typed interfaces, variance is mainly concerned with how objects can be substituted.

Covariant interface of type T indicates that its derivatives will only have methods that produce entities of type T. It is specified with the out keyword.

// covariant
interface IProducer<out T> {
    T Produce();
}

Conversely, contravariant interface of type T shows that its derivatives will only have methods that takes in object parameters of type T but not returning it. It is indicated by the in keyword.

// contravariant
interface IConsumer<in T> {
    void Consume(T obj);
}

Covariance

For example, a producer of type IProducer<Animal> cannot directly assign the return value to the Cat variable because Produce() returns Animal.

IProducer<Animal> producer;
Animal a = producer.Produce();
Cat b = producer.Produce(); // error

Conversely, if it's IProducer<Cat>, Produce() returns Cat, which can also be assigned to a variable of type Animal.

IProducer<Cat> producer;
Animal c = producer.Produce();
Cat d = producer.Produce(); // no issue

This means that it is safe for a subtype to be placed in the position of a supertype, and this is called covariance.

Cat : Animal ==> IProducer<Cat> : IProducer<Animal>

Contravariance

IConsumer<Animal> can consume not only Animal but also Cat.

IConsumer<Animal> consumer;
consumer.Consume(new Animal());
consumer.Consume(new Cat()); // no problem

Conversely, IConsumer<Cat> can only safely consume Cat and will throw an error if passed Animal.

IConsumer<Cat> consumer;
consumer.Consume(new Cat());
consumer.Consume(new Animal()); // error

Hence, they implies contravariance as they behaves in the opposite way.

Cat : Animal ==> IConsumer<Animal> : IConsumer<Cat>

Invariant Type

If a generic parameter does not have a mutation notation such as in or out, the type is invariant, meaning it does not allow substitution between subtypes.

Reference

Liskov substitution principle.ย Wikipedia. Retrieved 2024, March 26 from https://en.wikipedia.org/wiki/Liskov_substitution_principle
Leopold, N. Covariance and now Contravariance, do I really need to know this?https://morotsman.github.io/java/contravariance/the/liskov/substitution/principle/2020/07/17/java-contravariance.html
Horvat, Z. What is Covariance and Contravariance in C#.https://www.youtube.com/watch?v=Wp5iYQqHspg
Ssabae. ๊ณต๋ณ€์„ฑ, ๋ฐ˜๊ณต๋ณ€์„ฑ, ๋ฌด๊ณต๋ณ€์„ฑ์ด๋ž€?https://velog.io/@lsb156/covariance-contravariance