Feature Post

Top

Covariance and Contravariance

Covariance is when an object that is instantiated with a more derived type is assigned to an object that is instantiated with a less derived type.
For instance,

   1: IEnumerable<string> strings = new List<string>(); 

The assignment compatibility is preserved. Know that:

Invariant means, must use the exact type – be ‘in’ or ‘out’

Covariant means, parameter type can be of a derived class type; upcast’able; more specific to more general. Specified as output types: return types, 'out' params, get properties. Array types fall in this category.

Contravariant is opposite of covariance. Parameter type can be a parent class type; downcast'able; more general to specific type. Use base type where a child type was expected. Specified as input types: set, 'in' parameters, etc.

Covariance and contravariance (or simply “variance”) are advanced concepts. The motivation behind introducing and enhancing variance in C# was to allow generic interface and generic types (in particular, those defined in the Framework, such as IEnumerable<T>() to work more as we would expect.

Conventionally, array types are covariant. This means that X can be cast to Y if X subclasses Y. For instance:


   1: Bear[] bears = new Bear[3];
   2: Animal[] animals = bears; // OK

The downside of this reusability is that element assignments can fail at runtime:


   1: animals[0] = new Camel(); // Runtime error. Why, because Animal class is 
   2: //expecting Bears, notCamels!

Covariance (and contra variance) in interfaces is something that you typically consume: it’s less common that you need to write variant interfaces.

See following example:



   1: //Base type
   2: class Car { public string name = "Base car"; } 
   3:  
   4: //Sub type
   5: class Honda : Car { public Honda() { name = "Specific car: 
   6: Honda"; } } 
   7:  
   8: class Program
   9: {
  10:  delegate T Func<out T>(); /*Covariant; remember? Specified as 'out'put types*/
  11:  
  12: delegate void Action<in T>(T a);/*Contravariant; Specified as 'in'put types!*/
  13:  static void Main(string[] 
  14: args)
  15:  {
  16:  // Example: Covariance
  17:  Func<Honda> child1 = () => new Honda();
  18:  Func<Car> parent1 = child1; //Assignment compatibility is preserved 
  19:  
  20:  // Example: Contravariance
  21:  Action<Car> parent2 = (someArgs) => { Console.WriteLine(someArgs.name); };
  22:  Action<Honda> child2 = parent2;//Assignment compatibility is maintained 
  23:  child2(new Honda());//Prints, Specific car: Honda 
  24:  
  25: Console.ReadKey();
  26:  }
  27: }

The type system is free to allow us to safely upcast and downcast between those interfaces in the direction indicated by the keyword. You might like exploring more here.
It's really not a "save the day" sort of feature. It's more of a "it works the way I expect it to" feature - Eric Lippert
Enjoy, variants in life! (0: