Covariance

Covariance & Contravariance are one of the most confusing terms in C#, no surprise it’s very popular for geeky questions in job interviews. Any time someone mentions those terms, it takes quite a while to refresh my knowledge about it. So, I decided to write a couple posts explaining them in the simplest and shortest way, with pointing out key features.

Covariance:

Covariance is polymorphism expressed through generic types in interfaces and their implementations. It is expressed through assignments of generic types with more derived generic parameters into generic interfaces with less derived generic parameters.


Sounds complicated? 🙂 Let’s examine the following code snippet:

public class Program
{
    public interface ICurrencyProps<out TCurrency>
    {
        TCurrency Get();
    }

    public class CurrencyProps<TCurrency> : ICurrencyProps<TCurrency>
    {
        public TCurrency Get()
        {
            return default(TCurrency);
        }
    }

    public class Currency { }

    public class Euro : Currency { }

    public class Dollar : Currency { }

    static void Main(string[] args)
    {
        //normal polymorphism expressed by native language features
        Currency curr = new Euro();

        //covariance polymorphism expressed through interface generics 
        ICurrencyProps<Currency> baseProps = new CurrencyProps<Euro>();
    }
}

What happens in the last line of Main method? We have more derived generic type in right hand side declaration (Euro) and less derived generic type in left hand side declaration (Currency). Before covariance was introduced, ICurrencyProps<Currency> and CurrencyProps<Euro> were like apples and frogs – completely different types, because closed generic types are still just types.

With covariance in place, polymorphism is lifted up from dimension of matching classes by inheritance to dimension of maching generic interface with generic implementation by inheritance of generic type parameters. So, when you put OUT keyword in interface declaration, the above code is possible to compile. Since I use generic type parameter only as output value, baseProps.Get() would return Euro instance that would be declared under Currency type.

Further, it’s now easier to understand why covariance came with a cost:


Covariant generic types in interface definition are marked with OUT keyword.  Covariant generic types in interface implementation can be used only as output values.


Why is that so? Imagine there is no ‘output values only’ restriction – let’s try to use generic type parameter as input value:


public interface ICurrencyProps<out TCurrency>
{
    void Set(TCurrency currency);
}
//...
ICurrencyProps<Currency> baseProps = new CurrencyProps<Euro>();
//...
baseProps.Set(new Dollar());

Through new features of allowing covariant generic types assignment, we allowed another logically impossible scenario. Regardless of the declaration type of baseProps, it’s still esentially a CurrencyProps<Euro>(). The consequence is that Set() method accepts only something more than Euro. Dollar is everything but that! It doesn’t inherit from Euro, it inherits from Currency.
That’s why without OUT keyword you would get an syntax error while trying to even use covariant generic type as input parameter:

  • Invalid variance: The type parameter ‘TCurrency’ must be contravariantly valid on ‘Program.ICurrencyProps<TCurrency>.Set(TCurrency)’. ‘TCurrency’ is covariant.

Summary: When trying to understand and remember what is covariance, just recall few things that will fall in place:

  • it is related to generic interface definition and their implementation
  • it is defined by OUT keyword beside generic type parameters in interface definition
  • it opens new dimension for polymorphism over generic interface/implementation type assignment
  • it limits the options for covariant generic type parameters in interface implementation to be used only as output values.

Continue reading about Contravariance.