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:

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:

And using these types in code would look like this:

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:

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

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:

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

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:

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:

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:

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:

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

But what will happen if we write the below delegate:

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: