Notes
Covariant and Contravariant Interface
A brief explanation on variance in programming with examples in C#

Covariant and Contravariant 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 exists to compliment 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

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

If the IProducer takes in a type of higher hierarchical order, the method Produce will produce the higher type which isn't compatible with the derived type reference.

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

However, if the IProducer takes in a derived type of a higher hierarchy, the method Produce will fit in both the base and derived reference.

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

These means that a derived type is behaving the same as the base type, hence covariance.

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

Contravariance

The Consume method takes in the generic typed parameter of T and it is apparent to notice the difference; Now both the derived type and the base type fits in well in the equation as seen below.

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

The lesser derived type on the consumer have the opposite behaviour.

IConsumer<Cat> consumer;
consumer.Consume(new Cat());
consumer.Consume(new Animal()); // obviously does not work

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

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

Extras: Invariant Type

Interfaces with type T itself without any base or derived types can always satisfy both ends for consuming and producing. They are called the invariant type.

Reference