Covariance and Contravariance in .NET C#
Tags: dotnet, csharp, architecture, programming
Published: 2021-12-27 22:08:34

### Have hard time understanding it? Let me simplify it for you.
<br>
If it is so hard on you to understand what **Covariance** and **Contravariance** in **.NET C#** means, don’t feel ashamed of it, you are not alone.

It happened to me and many other developers. I even know experienced developers who either don’t know about them and are using them but still can’t understand them well enough.

From where I see it, this is happening because every time I come across an article talking about **Covariance** and **Contravariance**, I find it focused on some technical terminologies rather than being concerned about the reason why we have them in the first place and what we would have missed if they didn’t exist.

![Simple explanation of DotNet (.NET) C# Invariance, Covariance & Contravariance Cheat Sheet  CSharp Programming Software Development Best Practice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7ax1qow4hg7ctlv8wyq.png)

![Photo by [Tadas Sar](https://unsplash.com/@stadsa?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/8064/1*09u_9YpDdTA_SyZHFQYMaQ.jpeg)

## Microsoft’s Definition

If you check [Microsoft’s documentation](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/) for the **Covariance** and **Contravariance** in **.NET C#**, you would find this definition:
>  In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

Do you get it? do you like it?

You can search the internet and you will find tons of resources about this topic. You will come across definitions, history, when introduced, code samples,… and many others and this is not what you would find in this story. I promise you that what you would see here is different….

![Simple explanation of DotNet (.NET) C# Invariance, Covariance & Contravariance Cheat Sheet  CSharp Programming Software Development Best Practice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7ax1qow4hg7ctlv8wyq.png)

![Photo by [Rhys Kentish](https://unsplash.com/@rhyskentish?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/7750/1*KwMyZMxmrJFFAVwP6fBpZQ.jpeg)

## What are they actually?

Basically, what Microsoft did is that they added a small addition to the way you define your generic template type place holder, the famous *<T>*.

What you used to do when defining a generic interface is to follow the pattern public interface `IMyInterface<T> {…}`. After having Covariance and Contravariance introduced, you can now follow the pattern public interface `IMyInterface<out T> {…}` or public interface `IMyInterface<in T> {…}`.

Do you recognize the extra **out** and **in**?
Have you seen them somewhere else?
May be on the famous .NET public interface `IEnumerable<out T>`?
or the famous .NET public interface `IComparable<in T>`?

Microsoft introduced a new concept so that the compiler -at design time- would make sure that the types of objects you use and pass around generic members would not throw runtime exceptions caused by wrong type expectations.

Still not clear, right? Just bear with me... Let’s assume that the compiler doesn’t apply any design time restrictions and see what would happen.

![Simple explanation of DotNet (.NET) C# Invariance, Covariance & Contravariance Cheat Sheet  CSharp Programming Software Development Best Practice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7ax1qow4hg7ctlv8wyq.png)

![Photo by [Rick Monteiro](https://unsplash.com/@rickmontii?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/8000/1*yQ9Uk-RgSLzXhgXABBK2tA.jpeg)

## What if the compiler doesn’t apply any design time restrictions?

To be able to work on an appropriate example, let’s define the following:

```cs
public class A
{
	public void F1(){}
}

public class B : A
{
	public void F2(){}
}

public class C : B
{
	public void F3(){}
}

public interface IReaderWriter<TEntity>
{
	TEntity Read();
	void Write(TEntity entity);
}

public class ReaderWriter<TEntity> : IReaderWriter<TEntity> where TEntity : new()
{
	public TEntity Read()
	{
		return new TEntity();
	}

	public void Write(TEntity entity)
	{
	}
}
```

Looking into the code above, you will notice that:

 1. Class A has `F1()` defined.

 2. Class B has `F1()` and `F2()` defined.

 3. Class C has `F1()`, `F2()` , and `F3()` defined.

 4. The interface `IReaderWriter` has `Read()` which returns an object of type `TEntity` and `Write(TEntity entity)` which expects a parameter of type `TEntity`.

Then let’s define a `TestReadWriter()` method as follows:

```cs
public static void TestReaderWriter(IReaderWriter<B> param)
{
	var b = param.Read();
	b.F1();
	b.F2();

	param.Write(b);
}
```

### Calling TestReadWriter() when passing in an instance of IReaderWriter<B>

This should **work fine** as we are not violating any rules. `TestReadWriter()` is already expecting a parameter of type `IReaderWriter<B>`.

### Calling `TestReadWriter()` when passing in an instance of `IReaderWriter<A>`

*Keeping in mind the assumption that the compiler doesn’t apply any design time restrictions*, this means that:

 1. `param.Read()` would return an instance of class **A**, **not B**
 **=>** So, the `var b` would actually be of type **A**, **not B**
 **=>** This would lead to the `b.F2()` line to **fail** as the `var b` -which is actually of type **A**- does not have `F2()` defined

 2. `param.Write()` line in the code above would be expecting to receive a parameter of type **A**, not **B**
 => So, calling `param.Write()` while passing in a parameter of type **B** would both **work fine**

Therefore, since in the point **#1** we are expecting a runtime failure, then we can’t call `TestReadWriter()` with passing in an instance of `IReaderWriter<A>`.

### Calling `TestReadWriter()` when passing in an instance of `IReaderWriter<C>`

*Keeping in mind the assumption that the compiler doesn’t apply any design time restrictions*, this means that:

 1. `param.Read()` would return an instance of class **C**, not **B**
 **=>** So, the `var b` would actually be of type **C**, not **B**
 **=>** This would lead to the `b.F2()` line to **work fine** as the `var b` would have `F2()`

 2. `param.Write()` line in the code above would be expecting to receive a parameter of type **C**, not **B**
 => So, calling `param.Write()` while passing in a parameter of type **B** would **fail** because simply you can’t replace **C** with its parent **B**

Therefore, since in the point **#2** we are expecting a runtime failure, then we can’t call `TestReadWriter()` with passing in an instance of `IReaderWriter<C>`.

![Simple explanation of DotNet (.NET) C# Invariance, Covariance & Contravariance Cheat Sheet  CSharp Programming Software Development Best Practice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7ax1qow4hg7ctlv8wyq.png)

![Photo by [Markus Winkler](https://unsplash.com/@markuswinkler?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/7998/1*HxHoLEPKokFwd0_NMVnXUQ.jpeg)

Now, let’s analyze what we have discovered up to this moment:

 1. Calling `TestReadWriter(IReaderWriter<B> param)` when passing in an instance of `IReaderWriter<B>` is always fine.

 2. Calling `TestReadWriter(IReaderWriter<B> param)` when passing in an instance of `IReaderWriter<A>` would be fine if we don’t have the `param.Read()` call.

 3. Calling `TestReadWriter(IReaderWriter<B> param)` when passing in an instance of `IReaderWriter<C>` would be fine if we don’t have the `param.Write()` call.

 4. However, since we always have a mix between `param.Read()` and `param.Write()`, we would always have to stick to calling `TestReadWriter(IReaderWriter<B> param)` with passing in an instance of `IReaderWriter<B>`, nothing else.

 5. Unless…….

![Simple explanation of DotNet (.NET) C# Invariance, Covariance & Contravariance Cheat Sheet  CSharp Programming Software Development Best Practice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7ax1qow4hg7ctlv8wyq.png)

![Photo by [Hal Gatewood](https://unsplash.com/@halacious?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/10368/1*cIalJUmZN88XP3ZQZXBCBw.jpeg)

## The Alternative

What if we make sure that the `IReaderWriter<TEntity>` interface defines either `TEntity Read()` or `void Write(TEntity entity)`, not both of them at the same time.

Therefore, if we drop the `TEntity Read()`, we would be able to call `TestReadWriter(IReaderWriter<B> param)` with passing in an instance of `IReaderWriter<A>` or `IReaderWriter<B>`.

Similarly, if we drop the `void Write(TEntity entity)`, we would be able to call `TestReadWriter(IReaderWriter<B> param)` with passing in an instance of `IReaderWriter<B>` or `IReaderWriter<C>`.

This would be better for us as it would be less restrictive, right?

![Simple explanation of DotNet (.NET) C# Invariance, Covariance & Contravariance Cheat Sheet  CSharp Programming Software Development Best Practice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7ax1qow4hg7ctlv8wyq.png)

![Photo by [Agence Olloweb](https://unsplash.com/@olloweb?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/8576/1*n1N2ihMRmi_XX4st_Mz2xw.jpeg)

## Time for some Facts

 1. In the real world, the compiler -in design time- would never allow calling `TestReadWriter(IReaderWriter<B> param)` with passing in an instance of `IReaderWriter<A>`. You would get a compilation error.

 2. Also, the compiler -in design time- would not allow calling `TestReadWriter(IReaderWriter<B> param)` with passing in an instance of `IReaderWriter<C>`. You would get a compilation error.

 3. From point #1 and #2, this is called **Invariance**.

 4. Even if you drop the `TEntity Read()` from the `IReaderWriter<TEntity>` interface, the compiler -in design time- would not allow you to call `TestReadWriter(IReaderWriter<B> param)` with passing in an instance of `IReaderWriter<A>`. You would get a compilation error. This is because the compiler would not -implicitly by itself- look into the members defined into the interface and see if it is going to always work at runtime or not. You will need to do this by yourself through `<in TEntity>`. This acts as a promise from you to the compiler that all members in the interface would either don’t depend on `TEntity` or deal with it as an **input**, **not an output**. This is called **Contravariance**.

 5. Similarly, even if you drop the void `Write(TEntity entity)` from the `IReaderWriter<TEntity>` interface, the compiler -in design time- would not allow you to call `TestReadWriter(IReaderWriter<B> param)` with passing in an instance of `IReaderWriter<C>`. You would get a compilation error. This is because the compiler would not -implicitly by itself- look into the members defined into the interface and see if it is going to always work at runtime or not. You will need to do this by yourself through `<out TEntity>`. This acts as a promise from you to the compiler that all members in the interface would either don’t depend on `TEntity` or deal with it as an **output**, **not an input**. This is called **Covariance**.

 6. Therefore, adding `<out >` or `<in >` makes the compiler less restrictive to serve our needs, not more restrictive as some developers would think.

![Simple explanation of DotNet (.NET) C# Invariance, Covariance & Contravariance Cheat Sheet  CSharp Programming Software Development Best Practice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7ax1qow4hg7ctlv8wyq.png)

![Image by [Harish Sharma](https://pixabay.com/users/harishs-3407954/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3245793) from [Pixabay](https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3245793)](https://cdn-images-1.medium.com/max/3840/1*CBrnK4hi6OItpSE6CWctzA.jpeg)

## Summary

At this point, you should already understand the full story of **Invariance**, **Covariance** and **Contravariance**. However, as a quick recap, you can deal with the following as a **cheat sheet**:

 1. Mix between input and output generic type => Invariance => the most restrictive => can’t replace with parents or children.

 2. Added `<in >` => only **input** => **Contravariance** => **itself or replace with parents**.

 3. Added `<out >` => only **output** => **Covariance** => **itself or replace with children**.

![Image by [Ahmed Tarek](https://medium.com/@eng_ahmed.tarek)](https://cdn-images-1.medium.com/max/2058/1*85tBjg8Kkt-54vhvVr8tNQ.png)

Finally, I will drop here some code for you to check. It would help you practice more.
```cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetVariance
{
	class Program
	{
		static void Main(string[] args)
		{
			IReader<A> readerA = new Reader<A>();
			IReader<B> readerB = new Reader<B>();
			IReader<C> readerC = new Reader<C>();
			IWriter<A> writerA = new Writer<A>();
			IWriter<B> writerB = new Writer<B>();
			IWriter<C> writerC = new Writer<C>();
			IReaderWriter<A> readerWriterA = new ReaderWriter<A>();
			IReaderWriter<B> readerWriterB = new ReaderWriter<B>();
			IReaderWriter<C> readerWriterC = new ReaderWriter<C>();

			#region Covariance
			// IReader<TEntity> is Covariant, this means that:
			// 1. All members either don't deal with TEntity or have it in the return type, not the input parameters
			// 2. In a call, IReader<TEntity> could be replaced by any IReader<TAnotherEntity> given that TAnotherEntity
			// is a child -directly or indirectly- of TEntity

			// TestReader(readerB) is ok because TestReader is already expecting IReader<B>
			TestReader(readerB);

			// TestReader(readerC) is ok because C is a child of B
			TestReader(readerC);

			// TestReader(readerA) is NOT ok because A is a not a child of B
			TestReader(readerA);
			#endregion

			#region Contravariance
			// IWriter<TEntity> is Contravariant, this means that:
			// 1. All members either don't deal with TEntity or have it in the input parameters, not in the return type
			// 2. In a call, IWriter<TEntity> could be replaced by any IWriter<TAnotherEntity> given that TAnotherEntity
			// is a parent -directly or indirectly- of TEntity

			// TestWriter(writerB) is ok because TestWriter is already expecting IWriter<B>
			TestWriter(writerB);

			// TestWriter(writerA) is ok because A is a parent of B
			TestWriter(writerA);

			// TestWriter(writerC) is NOT ok because C is a not a parent of B
			TestWriter(writerC);
			#endregion

			#region Invariance
			// IReaderWriter<TEntity> is Invariant, this means that:
			// 1. Some members have TEntity in the input parameters and others have TEntity in the return type
			// 2. In a call, IReaderWriter<TEntity> could not be replaced by any IReaderWriter<TAnotherEntity>

			// IReaderWriter(readerWriterB) is ok because TestReaderWriter is already expecting IReaderWriter<B>
			TestReaderWriter(readerWriterB);

			// IReaderWriter(readerWriterA) is NOT ok because IReaderWriter<B> can not be replaced by IReaderWriter<A>
			TestReaderWriter(readerWriterA);

			// IReaderWriter(readerWriterC) is NOT ok because IReaderWriter<B> can not be replaced by IReaderWriter<C>
			TestReaderWriter(readerWriterC);
			#endregion
		}

		public static void TestReader(IReader<B> param)
		{
			var b = param.Read();
			b.F1();
			b.F2();

			// What if the compiler allows calling TestReader with a param of type IReader<A>, This means that:
			// param.Read() would return an instance of class A, not B
			//		=> So, the var b would actually be of type A, not B
			//		=> This would lead to the b.F2() line in the code above to fail as the var b doesn't have F2()

			// What if the compiler allows calling TestReader with a param of type IReader<C>, This means that:
			// param.Read() would return an instance of class C, not B
			//		=> So, the var b would actually be of type C, not B
			//		=> This would lead to the b.F2() line in the code above to work fine as the var b would have F2()
		}

		public static void TestWriter(IWriter<B> param)
		{
			var b = new B();
			param.Write(b);

			// What if the compiler allows calling TestWriter with a param of type IWriter<A>, This means that:
			// param.Write() line in the code above would be expecting to receive a parameter of type A, not B
			//		=> So, calling param.Write() while passing in a parameter of type A or B would both work

			// What if the compiler allows calling TestWriter with a param of type IWriter<C>, This means that:
			// param.Write() line in the code above would be expecting to receive a parameter of type C, not B
			//		=> So, calling param.Write() while passing in a parameter of type B would not work
		}

		public static void TestReaderWriter(IReaderWriter<B> param)
		{
			var b = param.Read();
			b.F1();
			b.F2();

			param.Write(b);

			// What if the compiler allows calling TestReaderWriter with a param of type IReaderWriter<A>, This means that:
			// 1. param.Read() would return an instance of class A, not B
			//		=> So, the var b would actually be of type A, not B
			//		=> This would lead to the b.F2() line in the code above to fail as the var b doesn't have F2()
			// 2. param.Write() line in the code above would be expecting to receive a parameter of type A, not B
			//		=> So, calling param.Write() while passing in a parameter of type A or B would both work

			// What if the compiler allows calling TestReaderWriter with a param of type IReaderWriter<C>, This means that:
			// 1. param.Read() would return an instance of class C, not B
			//		=> So, the var b would actually be of type C, not B
			//		=> This would lead to the b.F2() line in the code above to work fine as the var b would have F2()
			// 2. param.Write() line in the code above would be expecting to receive a parameter of type C, not B
			//		=> So, calling param.Write() while passing in a parameter of type B would not work
		}
	}

	#region Hierarchy Classes
	public class A
	{
		public void F1()
		{
		}
	}

	public class B : A
	{
		public void F2()
		{
		}
	}

	public class C : B
	{
		public void F3()
		{
		}
	}
	#endregion

	#region Covariant IReader
	// IReader<TEntity> is Covariant as all members either don't deal with TEntity or have it in the return type
	// not the input parameters
	public interface IReader<out TEntity>
	{
		TEntity Read();
	}

	public class Reader<TEntity> : IReader<TEntity> where TEntity : new()
	{
		public TEntity Read()
		{
			return new TEntity();
		}
	}
	#endregion

	#region Contravariant IWriter
	// IWriter<TEntity> is Contravariant as all members either don't deal with TEntity or have it in the input parameters
	// not the return type
	public interface IWriter<in TEntity>
	{
		void Write(TEntity entity);
	}

	public class Writer<TEntity> : IWriter<TEntity> where TEntity : new()
	{
		public void Write(TEntity entity)
		{
		}
	}
	#endregion

	#region Invariant IReaderWriter
	// IReaderWriter<TEntity> is Invariant as some members have TEntity in the input parameters
	// and others have TEntity in the return type
	public interface IReaderWriter<TEntity>
	{
		TEntity Read();
		void Write(TEntity entity);
	}

	public class ReaderWriter<TEntity> : IReaderWriter<TEntity> where TEntity : new()
	{
		public TEntity Read()
		{
			return new TEntity();
		}

		public void Write(TEntity entity)
		{
		}
	}
	#endregion
}
```
<br>
That’s it, hope you found reading this story as interesting as I found writing it.
<br>
This article was originally published [here](https://www.developmentsimplyput.com/post/variance-invariance-covariance-contravariance-dotnet)