Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Contravariance explained

First of, I have read many explanations on SO and blogs about covariance and contravariance and a big thanks goes out to Eric Lippert for producing such a great series on Covariance and Contravariance.

However I have a more specific question that I am trying to get my head around a little bit.

As far as I understand per Eric's explanation is that Covariance and Contravariance are both adjectives that describe a transformation. Covariant transformation is that which preserves the order of types and Contravariant transformation is one that reverses it.

I understand covariance in such a manner that I think most developers understand intuitively.

//covariant operation Animal someAnimal = new Giraffe();  //assume returns Mammal, also covariant operation someAnimal = Mammal.GetSomeMammal();  

The return operation here is covariant as we are preserving the size in which both Animal is still bigger than Mammal or Giraffe. On that note most return operations are covariant, contravariant operations would not make sense.

  //if return operations were contravariant   //the following would be illegal   //as Mammal would need to be stored in something   //equal to or less derived than Mammal   //which would mean that Animal is now less than or equal than Mammal   //therefore reversing the relationship   Animal someAnimal =  Mammal.GetSomeMammal();  

This piece of code of course would not make sense to most developers.

My confusion lies in Contravariant argument parameters. If you had a method such as

bool Compare(Mammal mammal1, Mammal mammal2); 

I have always learned that input parameters always force contravariant behavior. Such that if the type is used as an input parameter its behavior should be contravariant.

However what is the difference between the following code

Mammal mammal1 = new Giraffe(); //covariant Mammal mammal2 = new Dolphin(); //covariant  Compare(mammal1, mammal2); //covariant or contravariant? //or Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? 

By the same token that you can't do something like this you can't do

   //not valid    Mammal mammal1 = new Animal();     //not valid    Compare(new Animal(), new Dolphin()); 

I guess what I am asking is, what makes method argument passing a contravariant transformation.

Sorry for the long post, maybe I am understand this incorrectly.

EDIT:

Per some conversation below, I understand that for instance using a delegate layer can clearly show contravariance. Consider the following example

//legal, covariance Mammal someMammal = new Mammal(); Animal someAnimal = someMammal;  // legal in C# 4.0, covariance (because defined in Interface) IEnumerable<Mammal> mammalList = Enumerable.Empty<Mammal>(); IEnumerable<Animal> animalList = mammalList;  //because of this, one would assume //that the following line is legal as well  void ProcessMammal(Mammal someMammal);  Action<Mammal> processMethod = ProcessMammal; Action<Animal> someAction = processMethod; 

Of course this is illegal because someone can pass any Animal to someAction, where as the ProcessMammal expects anything thats Mammal or more specific ( lesser than Mammal ). Which is why someAction has to only be Action or anything more specific (Action)

However this is introducing a layer of delegates in the middle, is it necessary that for a contravariant projection to happen there has to be a delegate in the middle? And if we were to define Process as an interface we would declare the argument parameter as a contravariant type only because we wouldn't want someone to be able to do what I had shown above with delegates?

public interface IProcess<out T> {     void Process(T val); } 
like image 200
Stan R. Avatar asked Dec 26 '09 04:12

Stan R.


People also ask

What is difference between covariance and contravariance?

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

Why is contravariance useful?

This is useful whenever you want to access some common method or property of a collection which can contain items of one or more types derived from a base class. For example, you might have a hierarchy representing different categories of stock for a supermarket.

What is covariance and contravariance in generics in Java?

Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them.

Can you be declared as contravariant?

A type can be declared contravariant in a generic interface or delegate only if it defines the type of a method's parameters and not of a method's return type. In , ref , and out parameters must be invariant, meaning they are neither covariant nor contravariant.


1 Answers

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.

like image 181
Alexandra Rusina Avatar answered Sep 24 '22 23:09

Alexandra Rusina