Covariance And Contravariance CSharp DotNetCore

Dec 13th, 2019 - written by Kimserey with .

In programming languages supporting generic types, we sometimes come accross the terms Covariance and Contravariance. Those two terms describe how generic type paramters act and how they can be assigned. In today’s post, we will look at their definition together with examples.

The easiest way to understand the terms is through examples. C# uses the keywords out for covariance and in for contravariance on generic types.

For example,

1
2
3
4
public interface ITest<in T>
{
    void Do(T test);
}

and

1
2
3
4
public interface ITest<out T>
{
    T Do();
}

Let’s start first by looking at Covariance.

Covariance

To demonstrate covariance we start by creating an interface with the out keyword on the parameterized type.

1
2
3
4
public interface ITest<out T>
{
    T Do();
}

This is possible because our type T is only used as output.

We then define a type Message and a derived type TitledMessage:

1
2
3
4
5
6
7
8
9
public class Message
{
    public string Content { get; set; }
}

public class TitledMessage: Message
{
    public string Title { get; set; }
}

and then define an implementation of ITest<> with the derived type:

1
2
3
4
5
6
7
public class Test: ITest<TitledMessage>
{
    public TitledMessage Do()
    {
        return new TitledMessage { Title = "Hello", Content = "Hello world" };
    }
}

Covariance allows us to do the following assignment:

1
2
ITest<TitledMessage> x = new Test();
ITest<Message> y = x;

We can see how this is made possible as the output type is allowed to be a derived type. Without specifying <out T>, the compiler would have prevented us from making this assignment.

This provides flexibility as we saw we are able to assign a generic interface of a derived type to a generic interface of the less derived type. A good example of that is IEnumerable<out T>.

Contravariance

In order to demonstrate contravariance, we create an interface using the in:

1
2
3
4
public interface ITest<in T>
{
    void Do(T test);
}

and create an implementation:

1
2
3
4
5
6
7
public class Test: ITest<Message>
{
    public void Do(Message test)
    {
        Console.WriteLine(test.Content);
    }
}

Message here is setup as a contravariant type parameter, and is used only as input of the function Do(T test). This allows to do the following:

1
2
ITest<Message> x = new Test();
ITest<TitledMessage> y = x;

ITest<Message> can be assigned to ITest<TitledMessage>. We can see how this is made possible in the implementation as TitledMessage is a derived class from Message therefore any instance of TitledMessage will be able to be provided to Do as parameter.

That provides flexibility in the sense that C# is able to convert types implicitly.

Conclusion

Today we saw what in and out where used for in C#. out being used for Covariance and in for Contravariance, we went through two examples showing the differences and how they could be used. I hope this demystify the terms and I see you on the next post!

External Sources

Designed, built and maintained by Kimserey Lam.