Author avatar

Dániel Szabó

Covariance and Contravariance for C#

Dániel Szabó

  • Dec 12, 2019
  • 4 Min read
  • 33 Views
  • Dec 12, 2019
  • 4 Min read
  • 33 Views
Languages Frameworks and Tools
C#

Introduction

With the introduction of C# version 4.0, two new features were added to the already rich set of tools the language provides to solve different problems. The features are covariance and contravariance. This guide will clarify those terms, and show you in real code how they enrich the feature set and allow developers to provide solutions to problems that previously required a workaround.

The co and contra-variances refer to how you treat an object. Immutable collection classes like lists are covariant when you are only allowed to push elements in if they are of type T or its subtype. Covariance comes into picture when you are working with mutable collections.

Variance as a term means a property of operators that act on specific types and the ordering of operators. The co prefix means "together with", and informs you that the operator preserves the ordering of types when compared to its opernands. The contra prefix means "against" something, and tells you that the operator reverses the ordering of types when compared to its operands.

Variance: Either Co or Contra

The following problem revolves around Servers and Network devices. Let's say you have a list of devices that have names and states. When making a list, include the devices you intend to process and ensure the devices have unique names and states.

Here is a demonstration for covariance:

1
List<Devices> switches = GetSwitches();
csharp

With C# 3.0, this will not compile as the compiler cannot figure out what you can or cannot do with the switches collection after you have it. Nothing would prevent you from adding a server to your collection, which means the following is not guaranteed to not happen.

1
switches.Add(new Server("Domain Controller","up"));
csharp

The compiler refuses to compile the code. This demonstrates contravariance.

In the following example, the compiler will allow the code, even though the method returns List< Devices >. This is because all switches are devices.

1
2
List<Switch> switches = GetAccess();
switches.Add(new Switch("DC-CORE-1","up"));
csharp

We can change types.

1
2
List<Devices> switches = GetAccess();
switches.Add(new Switch("DC-CORE-1","up"));
csharp

However this is also ambiguous because you could mean the following as well:

1
2
List<Switch> switches = GetAccess();
Switch dccore = switches[0];
csharp

Version 4.0 introduced two new keywords: in and out. They refer to the kind of arguments that can be placed in certain positions. The in marks input-position, the out marks output-position.

This allows us to code our original intentions.

1
IEnumerable<Devices> devices = GetSwitches();
csharp

The List has both the out and in directions, meaning it is neither co- nor contra-variant. Take the following:

1
2
3
interface IOnlyDevices<in T>{
	void Add(T value);
}
csharp

The simple interface makes it possible to do the following:

1
2
IOnlyDevices<Switch> switches = GetAccess();
switches.Add(new Switch("DC-CORE-1","up"));
csharp

So now the last statement is safe and unambiguous.

Conclusion

Contravariance allows you to utilize a less derived type than originally specified, and covariance lets you use a more derived type. In a sense, the reason they were brought to the C# language is so you can extend arrays, delegate types and generic types with polymorphistic features. Choose co- or contra-variance based on the type of data you are working with.

0