Contravariance explained

Update: Ooops. As it turned out, I mixed up variance and “assignment compatibility” in my initial answer. Edited the answer accordingly. Also I wrote a blog post that I hope should answer such questions better: Covariance and Contravariance FAQ

Answer: I guess the answer to your first question is that you don’t have contravariance in this example:

bool Compare(Mammal mammal1, Mammal mammal2); 
Mammal mammal1 = new Giraffe(); //covariant - no             
Mammal mammal2 = new Dolphin(); //covariant - no            

Compare(mammal1, mammal2); //covariant or contravariant? - neither            
//or             
Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? - neither

Furthermore, you don’t even have covariance here. What you have is called “assignment compatibility”, which means that you can always assign an instance of a more derived type to an instance of a less derived type.

In C#, variance is supported for arrays, delegates, and generic interfaces. As Eric Lippert said in his blog post What’s the difference between covariance and assignment compatibility? is that it’s better to think about variance as “projection” of types.

Covariance is easier to understand, because it follows the assignment compatibility rules (array of a more derived type can be assigned to an array of a less derived type, “object[] objs = new string[10];”). Contravariance reverses these rules. For example, imagine that you could do something like “string[] strings = new object[10];”. Of course, you can’t do this because of obvious reasons. But that would be contravariance (but again, arrays are not contravariant, they support covariance only).

Here are the examples from MSDN that I hope will show you what contravariance really means (I own these documents now, so if you think something is unclear in the docs, feel free to give me feedback):

  1. Using Variance in Interfaces for Generic Collections

    Employee[] employees = new Employee[3];
    // You can pass PersonComparer, 
    // which implements IEqualityComparer<Person>,
    // although the method expects IEqualityComparer<Employee>.
    IEnumerable<Employee> noduplicates =
        employees.Distinct<Employee>(new PersonComparer());
    
  2. Using Variance in Delegates

    // Event hander that accepts a parameter of the EventArgs type.
    private void MultiHandler(object sender, System.EventArgs e)
    {
       label1.Text = System.DateTime.Now.ToString();
    }
    public Form1()
    {
        InitializeComponent();
        // You can use a method that has an EventArgs parameter,
        // although the event expects the KeyEventArgs parameter.
        this.button1.KeyDown += this.MultiHandler;
        // You can use the same method 
        // for an event that expects the MouseEventArgs parameter.
        this.button1.MouseClick += this.MultiHandler;
     }
    
  3. Using Variance for Func and Action Generic Delegates

     static void AddToContacts(Person person)
     {
       // This method adds a Person object
       // to a contact list.
     }
    
     // The Action delegate expects 
     // a method that has an Employee parameter,
     // but you can assign it a method that has a Person parameter
     // because Employee derives from Person.
     Action<Employee> addEmployeeToContacts = AddToContacts;
    

Hope this helps.

Leave a Comment