Covariance and Contravariance in C#
Tags: programming, coding, oop, csharp
Published: 2023-06-20 07:15:35

The world of object-oriented programming is filled with intricate concepts, among which covariance and contravariance hold significant roles. These principles define the permissible type relationships in programming languages, especially when dealing with generic types. 

## Covariance

Covariance allows the use of a derived type in place of a base type. It is supported for reference type parameters in method returns, arrays, delegates, and generic interfaces.

Consider the following example with a base class `Animal` and a derived class `Dog`. If we have a method that returns an `Animal`, we can have it return a `Dog` without problems, since `Dog` is a derivative of `Animal`.

```csharp
public class Animal { }
public class Dog : Animal { }
public class Cat : Animal { }

public Animal GetAnimal()
{
    return new Dog(); // This operation is allowed
}
```

However, you can not do the opposite, i.e., have it return an `Animal` when you expect a `Dog`:

```csharp
public Dog GetDog()
{
    return new Animal(); // This operation is NOT allowed
}
```

Regarding generic types, covariance is only allowed for read operations on reference types:

```csharp
IEnumerable<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs; // This operation is allowed
```

However, it is not possible to use an `IEnumerable<Animal>` where an `IEnumerable<Dog>` is expected:

```csharp
IEnumerable<Animal> animals = new List<Animal>();
IEnumerable<Dog> dogs = animals; // This operation is NOT allowed
```

## Contravariance

Contravariance is the concept opposite to covariance. It allows the use of a base type in place of a derived type. Contravariance is supported for reference type parameters in method parameter positions, delegates, and generic interfaces.

Consider the following example with a delegate:

```csharp
delegate void Action<in T>(T arg);

static void Main()
{
    Action<Animal> animalAction = (Animal a) => Console.WriteLine(a);
    Action<Dog> dogAction = animalAction; // This operation is allowed
    dogAction(new Dog()); // Prints "Dog"
}
```

In this example, the delegate accepts an `Animal` type. Since `Dog` is a derivative of `Animal`, we can assign `animalAction` to `dogAction`. However, we can't do the opposite:

```csharp
Action<Dog> dogAction = (Dog d) => Console.WriteLine(d);
Action<Animal> animalAction = dogAction; // This operation is NOT allowed
```

In summary, contravariance allows only write operations on reference types.

## Invariance

If a type is neither covariant nor contravariant, it is said to be invariant. An invariant generic type maintains the exact identity of the type parameter. This means you cannot use a different type than the one specified as the parameter of the generic type.

For instance, in the generic class `List<T>`, `T` is invariant. This means you can not use a `List<Animal>` where you expect a `List<Dog>`, or vice versa, even though `Dog` is a derived type of `Animal`.

```csharp
List<Animal> animals = new List<Dog>(); // This operation is NOT allowed
```

Similarly, if you have a method that accepts a `List<Animal>`, you can not pass a `List<Dog>`:

```csharp
void ProcessAnimals(List<Animal> animals) { /*...*/ }

List<Dog> dogs = new List<Dog>();
ProcessAnimals(dogs); // This operation is NOT allowed
```

Although invariance might seem limiting, it is essential for type safety. Without invariance, you could introduce type errors into your code, as in the following example:

```csharp
List<Animal> animals = new List<Dog>(); // Suppose this operation was allowed
animals.Add(new Cat()); // This would be an error because we're trying to add a Cat to a list of Dogs
```

Invariance ensures that these situations do not occur, preserving the integrity and safety of your code.

## Conclusion

Covariance and contravariance might sound complicated, but they are really helpful in making your code flexible. C# strongly supports these ideas, and you can use them in many situations. But you need to be careful, as using them without fully understanding them can cause confusing errors. So, it is important to really understand what covariance and contravariance mean before you start using them to boost your code's potential.

## References

- [Covariance and Contravariance (C#)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/)