.NET Backend

What are covariance and contravariance in C#?

Sometimes it happens that we use mechanisms/features of specific language without beeing aware of it. That’s fine, but if you’ll want to discuss your code in a future to the audience or coworkers, soon or later you’ll be forced to learn it and understand (or at least name it using technical nomenclature). Therefore, today I’m going to discuss two related „mechanisms” of C# which are covariance and contravariance.

 

Covariance

Covariance is a type conversion from the specific type to the more general (base). Here’s the simple example:

 

class Shape { }

class Rectangle : Shape { }

class Square : Rectangle { }

class Test
{
    void TestCovariance()
    {
        Rectangle recatngle1 = new Square(); // Compiles
        Rectangle recatngle2 = new Rectangle(); // Compiles
        Rectangle rectangle3 = new Shape(); //Error
    }

}

 

This looks pretty obvious:

  • Not every shape must be a rectangle
  • Every rectangle is a rectangle
  • Every Square is also a rectangle

 

In this case, we can say that Square class is covariant to the Rectangle class. In C# all values returned from the methods are covariant. Therefore, this code would compile:

 


class Test
{
    void TestMethodReturnedValuesCovariance()
    {
        Rectangle rectangle = GetRectangle();
        Shape shape = GetRectangle();
    }

    Rectangle GetRectangle()
    {
        return new Rectangle();
    }

}

 

This one might be really handy in your code, but in some cases, covariance may be the cause of a tricky exception. Why tricky? Let’s discuss the following implementation:

 


public void TestArrayVariance()
{            
    string[] stringArray = new string[2];
    object[] objectArray = stringArray;

    objectArray[1] = new Guid();
}


 

So, in this case, we signed string array to the object array (due to the fact that string inherits from object class). As you can see it’s almost identical example to the one from the beginning of this article. Because in C# we can assign almost everything to the object, we used that to insert to our array new Guid. Guess what? This code compiles! But wait! Don’t event think that we can create a „multi-type” structure because of covariance. Here’s what happens when we’d run this:

 

exc

 

As presented, an exception has been thrown in the runtime, so it’s not that easy 馃槈 But be aware of that kind of mistakes when using covariance.

 

Contravariance

As most of you probably guess, contravariance is the opposite of the previous mechanism. It’s a type conversion from the general to the more specific. In C# all methods parameters are contravariant. Therefore compilation of the following code would finish with presented results:

 


class Test
{
    void TestMethodParamsContravariance()
    {
        SomeMethod(new Shape()); //Error
        SomeMethod(new Rectangle()); //Compiles
        SomeMethod(new Square()); //Compiles
    }

    public void SomeMethod(Rectangle rectangle)
    {

    }
}

 

Covariance and Contravariance in generics

So far, both covariance and contravariance seem to be kinda helpful in many cases. But we come to the another question. Does generics support them? The answer is… YES! Let’s take a look at the example below:

 


interface IVariance<T> {}

class Covariant
{
    public void Test()
    {
        IVariance<Shape> shape = GetRectangle(); // Error
        IVariance<Rectangle> rectangle = GetRectangle(); // Compiles
        IVariance<Square> square = GetRectangle(); // Error
    }

    IVariance<Rectangle> GetRectangle()
    {
        return null;
    }
}

 

In the presented case only one declaration is correct. That’s because by default generic parameters are invariant which means nothing more than „give me exactly that type, I’m not interested in any other”. However, we can change that easily. Let’s start with covariance:

 


interface IVariance<out T> {}

class Covariant
{
    public void Test()
    {
        IVariance<Shape> shape = GetRectangle(); // Compiles
        IVariance<Rectangle> rectangle = GetRectangle(); // Compiles
        IVariance<Square> square = GetRectangle(); // Error
    }

    IVariance<Rectangle> GetRectangle()
    {
        return null;
    }
}


 

All we did was adding out keyword before T. That tells the C# compiler that beyond T-type, the IVariance interface accepts also more general type. It’s also worth noting that in that example the out keyword is not related to its original meaning (I mean out parameters). But for me, it’s a good naming choice since it’s quite easy to associate it with the covariance mechanism (out – specific type and all above the hierarchy). Let’s move forward to the contravariance usage in generics:

 


interface IVariance<in T> {}

class Covariant
{
    public void Test()
    {
        IVariance<Shape> shape = GetRectangle(); // Error
        IVariance<Rectangle> rectangle = GetRectangle(); // Compiles
        IVariance<Square> square = GetRectangle(); // Compiles
    }

    IVariance<Rectangle> GetRectangle()
    {
        return null;
    }
}


 

Instead of out keyword, we used in. Once again we can associate that pretty easily (in – specific type and all below the hierarchy).

 

Summary

The last question is: do we really need it? YES! Using mentioned mechanism saves a lot of time and code. If you want to take a look at the more „practical” example go read my CQRS/ES series where I combined contravariance and reflection to implement event sourcing inside the aggregate root. Here’s the link 馃檪 I know that for most of you that could be nothing special, but I know that many developers use it non-consciously. So even if you knew it, now you can also name it – like a pro! As always, I encourage you to follow me on Twitter and Facebook so you’ll be able to read all upcomming posts!