Contravariance

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 of posts explaining them in the simplest and shortest way, with pointing out key features.

Contravariance:

 

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

 

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


    class Program
    {
        public interface ICurrencyProps<in TCurrency>
        {
            void Set(TCurrency currency);
        }

        public class CurrencyProps<TCurrency> : ICurrencyProps<TCurrency>
        {
            public void Set(TCurrency currency) {  }
        }

        public class Currency { }

        public class Euro : Currency { }

        static void Main(string[] args)
        {
            //we cannot use polymorphism in the following way (we all know that)

            Euro eur = new Currency();

            //but with contravariance, we can do something like this:

            ICurrencyProps<Euro> euroProps = new CurrencyProps<Currency>();

            euroProps.Set(new Euro());
        }
    }

 

What happens in the second last line of Main method? We have less derived generic type in right hand side declaration (Euro) and more derived generic type in left hand side declaration (Currency). Before contravariance was introduced, ICurrencyProps<Currency> and CurrencyProps<Euro> were like apples and frogs – completely different types, because closed generic types are still just types. With contravariance 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 IN keyword in interface declaration, the above code is possible to compile.

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

 

Contravariant generic types in interface definition are marked with IN keyword.  Contravariant generic types in interface implementation can be used only as input values.

 

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


   public interface ICurrencyProps<in TCurrency>
   {
       TCurrency Get();
   }
   //...
   ICurrencyProps<Euro> euroProps = new CurrencyProps<Currency>();
   //...
   var euroCurr = euroProps.Get();

 

Through new features of allowing contravariant generic types assignment, we allowed another logically impossible scenario. Regardless of the declaration type of euroProps, it’s still esentially a CurrencyProps<Currency>(). The consequence is that Get() method will return nothing less then Currency. But it’s not good enough for us! We need nothing less then Euro.
That’s why without IN keyword you would get an syntax error while trying to even use contravariant generic type as output parameter:

 

  • Invalid variance: The type parameter ‘TCurrency’ must be covariantly valid on ‘Program.ICurrencyProps.Get(TCurrency)’. ‘TCurrency’ is contravariant

 

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

  • it is related to generic interface definition and their implementation
  • it is defined by IN 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 contravariant generic type parameters in interface implementation to be used only as input values

Continue reading about Covariance.

Leave a Reply

Your email address will not be published. Required fields are marked *