Channels ▼

Paul Kimmel

Dr. Dobb's Bloggers

Understanding Covariance and Contravariance

June 17, 2010

I believe in the idea that everyone is given gifts in different proportions. My gift was the gift of knowledge. I can learn things or figure them out pretty quickly except for instructions for assemble furniture. That is why it is especially frustrating for me when I don't intuitively grasp a concept. It doesn't happen that often, especially where programming concepts are concerned, but it does happen. The most recent I am not sure I get it moment occurred when I was trying to figure out what the heck contravariance and covariance in .NET 4.0 are for. There are a lot of posts and reposts that attempt to describe these abstruse concepts, but a lot of that information adds to the confusion. Fortunately for me Eric Lipper and Charlie Calvert and their respective posts on the subject helped set me straight. (Of course, I am not sure I get everything about variance and any mistakes are mine, but I have tried to boil it down here.)

According to the Eric Lippert the math is based on Category theory. I took a whole lot of math but not category theory. Eric does a great job explaining the math principles in his blog post ";What's the difference between covariance and assignment compatibility?"; here http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx, so I won't repeat that information. Instead let's skip to a brief explanation as it applies to .NET.

Assignment compatibility is that aspect of programming that lets you have a parent class like Music and a child class like Rap, declares a Music type and assign it to an instance of Rap.

class Music{} class Rap : Music{} Music Music = new Rap();

Without assignment compatibility polymorphism wouldn't work and consequently OO programming wouldn't work very well. Covariance preserves assignment compatibility and contravariance reverses it. This doesn't help me very much. I don't know about you. If you check the MSDN help it indicates that covariance is the ability to assign a narrower type-read child-to a wider type-read parent, and contravariance permits assigning a wider type to a narrower type. Very simplistically think covariance supports Music music = new Rap() and Rap rap = new Music(). Of course, Eric in the aforementioned blog says that that is not all covariance means; that the simple description has more to do with assignment compatibility. Eric Lippert and Charlie Calvert's many blogs on the subject go into a lot of detail, but I want to boil it down some more so will make things as simple as I understand them and will use just one instance where they appear, generic delegates.

Assignment Compatibility

Assignment compatibility means when it is okay to assign a more derived type-child-to a parent type. Suppose I define a generic delegate delegate T MyFunc() and initialize it with a Lambda Expression-Lambdas are used here for convenience-to return an instance of a Rap. Assignment compatibility means it is acceptable to declare an Music and assign it to an instance of a Rap and of course I can assign a method that returns a Rap to an instance of a function that returns a Rap. Here is some code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VarianceDemo
{
  class Program
  {
    delegate T MyFunc<T>();

    static void Main(string[] args)
    {
      // assignment compatibility
      MyFunc<Rap> returnRap = () => new Rap();
      Music Music = returnRap();
      Console.ReadLine();
      
    }
  }
  class Music{}
  class Rap : Music{}
}

This is all copacetic and works as one intuitively expects. Where things go haywire is if you attempt to define a second instance of the generic delegate where T is an Music and assign the generic Rap delegate to the second delegate:

MyFunc<Music> returnMusic = returnRap;

What the heck? Rap is a kind of Music (to some people, including me) and one would think this would work. It didn't work in earlier versions of .NET, but it does work in .NET 4.0 and it is an example of covariance. Simply change the delegate parameter from T to out T and covariance is supported. Here is the revised code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VarianceDemo
{
  class Program
  {
    delegate T MyFunc<out T>();

    static void Main(string[] args)
    {
      //covariance
      // assignment compatibility
      MyFunc<Rap> returnRap = () => new Rap();
      Music Music = returnRap();
      MyFunc<Music> returnMusic = returnRap;
      
    }
  }

  class Music{}
  class Rap : Music{}
}

Notice that MyFunc now defines T as an out parameter. Adding out to the generic parameter makes T covariant and now the intuitive assignment to the wider Music delegate from the narrower Rap delegate works.

Contravariance is the ability (roughly) to reverse the order of assignment above. One might intuitively think that it is acceptable to assign a generic delegate that performs on operation on a Music object to an operation that performs an operation on a Rap object, but pre-.NET 4.0 this wouldn't worth either. Suppose you have a generic delegate

delegate void MyAction<T>(T t);

and initialize it to

MyAction<Music> printMusic = (a)=>Console.WriteLine(a);

and then try to assign it a delegate that acts on a Rap object

MyAction<Rap> printRap = printMusic;

as defined this won't compile. However, if you change the generic parameter T to an in parameter then the code compiles and works as expected. Here is the solution demonstrating contravariance.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VarianceDemo
{
  class Program
  {
    delegate void MyAction<in T>(T t);

    static void Main(string[] args)
    {

      // contravariance
      MyAction<Music> printMusic = (a)=>Console.WriteLine(a);
      MyAction<Rap> printRap = printMusic;

      Console.ReadLine();
      
    }
  }

  class Music{}
  class Rap : Music{}
}

Suppose further that you defined a class Classical that inherits from Music. The compiler does a good job checking to make sure that the actual contravariant assignment makes sense. As a result you could not assign a generic delegate that accepts a Classical object parameter to a delegate that accepts a Rap generic parameter.

// this is invalid
MyAction<Classical> printClassical = (a) => Console.WriteLine(a);
MyAction<Rap> printRap2 = printClassical;

For covariance think the out modifier, and for contravariance think the in modifier. The net effect is that assignment compatibility is extended and preserved where it feels more intuitive and you can reverse the assignment order when it makes sense to do so.What the heck are covariance and contravariance? In a simple sense they preserve assignment compatibility in a more intuitive manner between parent and child relationships.

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 


Video