C#, Tech

Cannot implicitly convert type ‘Abc<Derived>’ to ‘IAbc<Base>’ – Contravariance vs Covariance – part 2.

Today I will write about the way variance (<in T> and <out T>) influences type checking in C#. Yes, the fact that one type can be passed to a generic method that requires another type, depends on these 2 small keywords we write before ‘T’ (or whatever ?) in interface header. BTW this article is the continuation of my previous post that makes a dummy introduction to the variance – if you haven’t seen it, then you should :P. Not because I wrote anything important there, but because my google analytics statistics will grow. So as you can see – it’s worth it :P.

The golden rule of variance

Let’s assume we have these 2 classes:

public class Car : Vehicle
{
   …
}

public class Vehicle
{
   …
}

If we have Covariance (<out T>) then:

  • we can pass a Car where a Vehicle is expected  

If we have Contravariance (<inT >) then:

  • we can use a Vehicle where a Car is expected

Let’s dive into this mud – contravariance?

For the beginning, have a peek at contravariant generic interface and a class implementing it:

public interface IGarageManager<in T>
{
   void Park(T item);
}
public class GarageManager<T> : IGarageManager<T>
{
   List<T> itemsInGarage;
   public GarageManager() => itemsInGarage = new List<T>();
   public void Park(T item) => itemsInGarage.Add(item);
}

And using these types in code would look like this:

IGarageManager<Vehicle> vehicleGarageManager = new GarageManager<Car>();	//(1)
IGarageManager<Car> carGarageManager = new GarageManager<Vehicle>();		//(2)

How do you think, which of the above 2 lines will cause compilation error? 😛

Tick-tock
Tick-tock
Tick-tock

The answer is… The line with comment (1) will cause the following error:

Cannot implicitly convert type ‘GarageManager<Car>’ to ‘IGarageManager< Vehicle>’. An explicit conversion exists (are you missing a cast?)

But what if we remove ‘in’ keyword in interface <in T>? Well, both of the lines in the above snippet will cause compilation errors:

Cannot implicitly convert type ‘GarageManager<Car>’ to IGarageManager< Vehicle>’. An explicit conversion exists (are you missing a cast?)

Cannot implicitly convert type ‘GarageManager<Vehicle>’ to ‘IGarageManager< Car>’. An explicit conversion exists (are you missing a cast?)

Tl;dr

I know, it’s boring. And you probably only scanned the above paragraphs. So here you are – the sum up of the above – introducing contravariance into our generic interface will allow us to use less specific type (a Vehicle) where more specific type (a Car) is expected. Without contravariance, this relation needs to be 1-1 (we pass a Car where a Car is expected and we pass a Vehicle where a Vehicle is expected).

The second jump into muddy puddle – covariance

Let’s have a look at covariance now (<out T>). Assuming we have the following interface and a class implementing it:

public interface IVehicleWashManager<out T>
{
   IEnumerable<T> GetAllWashed();
}
public class VehicleWash<T> : IVehicleWashManager<T>
{
   List<T> itemsWashed;
   public VehicleWash() => itemsWashed = new List<T>();
   public IEnumerable<T> GetAllWashed()
   {
      return itemsWashed;
   }
}

And of course, let’s try these types out:

IVehicleWashManager<Vehicle> vehicleWash = new VehicleWash<Car>();	//(1)
IVehicleWashManager<Car> carWash = new VehicleWash<Vehicle>();		//(2)

Again, one of the above lines causes error – which one? 😛

Tick-tock
Tick-tock
Tick-tock

Of course, the line with (2) in a comment will end compilation with this error:

Cannot implicitly convert type ‘VehicleWash<Vehicle>’ to ‘IVehicleWashManager<Car>’. An explicit conversion exists (are you missing a cast?)

Let’s again remove the ‘out’ keyword from interface’s definition and try to compile the code.

Yeah, again, we have 2 errors instead of one:

Cannot implicitly convert type ‘VehicleWash<Vehicle>’ to ‘IVehicleWashManager< Car>’. An explicit conversion exists (are you missing a cast?)

Cannot implicitly convert type ‘VehicleWash< Car>’ to ‘IVehicleWashManager< Vehicle>’. An explicit conversion exists (are you missing a cast?)

Tl;dr

So again – introducing covariance into our generic interface will allow us to use more specific type (a Car) where less specific type (a Vehicle) is expected. Without covariance, this relation needs to be 1-1 (we pass a Car where a Car is expected and we pass a Vehicle where a Vehicle is expected).

Do we even use it in everyday life?

Hell yeah! And you don’t even know, how often! Just try the below:

IEnumerable<Vehicle> vehicles = new List<Car>();		//(1)
IEnumerable<Car> cars = new List<Vehicle>();			//(2)

How do you think, which line will cause a compilation error? If you don’t know, just have a look at IEnumerable interface definition:

public interface IEnumerable<out T> : IEnumerable
{
   IEnumerator<T> GetEnumerator();
}

As you can see, we have covariance here (<out T>)… Yep, that’s why we cannot assign a list of Cars to the IEnumerable type that expects Vehicles only. So, the line with (2) in comment will not compile!

The same situation can occur when we use one of the following interfaces:

IEnumerable<out T> 
IEnumerator<out T> 
IQueryable<out T>
IGrouping<out TKey,out TElement>

So keep that in mind.

But what about a cotravariance (<in T>)? Yes, of course we can find it in .net guts too!

Below I put some well-known interfaces:

IComparer<in T>
IEqualityComparer<in T>
IComparable<in T>

Variance in delegates

What is more, we can find variance not only in interfaces but also in delegates.

Just take a look at the below code:

Action<Vehicle> PrintPrice = (Vehicle c) => Console.WriteLine(c.Price);

PrintPrice(new Vehicle());
PrintPrice(new Car());

How do you think, will this code compile?

Tick-tock
Tick-tock
Tick-tock

Yeah! The code will compile and that’s because T passed to Action<T> is contravariant! Let’s look into its guts:

public delegate void Action<in T>(T obj);

So, we can pass a more specific type where we expect a less specific.

But what will happen if we write the below delegate:

Action<Car> PrintPrice2 = (Car c) => Console.WriteLine(c.NumberOfWheels);
PrintPrice2(new Vehicle());		//(1)
PrintPrice2(new Car());		        //(2)

Well, the answer is… The compiler, as a good friend, will not let us do stupid things ? and will mark line with (1) in comment as the causing the below:

Argument 1: cannot convert from ‘BlogVariance.Vehicle’ to ‘BlogVariance.Car’

Summary

Variance makes generic interfaces more flexible and flexibility is cool ?!

Of course, it will not work on primitives – variance requires reference types to work. Ok, let’s admit that, it’s not a surprise :P.

All we need to remember from this post can be said with the below 2 sentences:

When we see <out T> in generic interface, we can use derived type where base type is expected.

When we see <in T> in generic interface, we can use base type where derived type is expected.

At the very end, let me stress the fact, that variance was introduced in C# 4, so quite long time ago but I bet, not many people have ever seen it ?. Btw did you?

Photo by Markus Spiske on Unsplash

Share this: