views:

714

answers:

4

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);
}
+1  A: 

(Edited in response to comments)

This MSDN article on the topic described covariance and contravariance as it applies to matching a function to a delegate. A variable of the delegate type:

public delegate bool Compare(Giraffe giraffe, Dolphin dolphin);

could (because of contravariance) be populated with the function:

public bool Compare(Mammal mammal1, Mammal mammal2)
{
    return String.Compare(mammal1.Name, mammal2.Name) == 0;
}

From my reading, it doesn't have to do with calling the function directly, but matching functions with delegates. I'm not sure that it can be boiled down to the level you demonstrate, with individual variables or object assignments being contravariant or covariant. But the assignment of a delegate uses contravariance or covariance in a way that makes sense to me according to the linked article. Because the delegate's signature contains more derived types than the actual instance, this is referred to as "contravariance", something separate from "covariance" in which a delegate's return type is less derived than the actual instance.

BlueMonkMN
That's certainly *not* the only area to which it applies. That's all that's discussed in your article because its title it "Covariance and Contravariance **in delegates**". It doesn't mention anything else much in the same way that something titled "Mating in primates" wouldn't talk about how salmon spawn; that doesn't mean that they don't.
Adam Robinson
It's still a useful article and a useful comment, whether or not his understanding of the topic is correct.
Jim Schubert
@Jim: It's a useful comment, but "covariance and contravariance only applies when matching a function to a delegate" is simply false and only serves to add confusion (via misinformation) to an already VERY widely misunderstood topic.
Adam Robinson
The article you are referring to is from .NET 2.0 docs. At that time, there was only variance in delegates. If you take a look at the .NET 4.0 version of this article (http://msdn.microsoft.com/en-us/library/ms173174(VS.100).aspx), you will see that it is now a part of a bigger doc set related to covariance and contravariance.
Alexandra Rusina
+5  A: 

My understanding is that it is not subtype relationships which are co/contra-variant but rather operations (or projections) between those types (such as delegates and generics). Therefore:

Animal someAnimal = new Giraffe();

is not co-variant, but rather this is just assignment compatibility since the type Giraffe is 'smaller than' the type Animal. Co/contra-variance becomes an issue when you have some projection between these types, such as:

IEnumerable<Giraffe> giraffes = new[] { new Giraffe(); };
IEnumerable<Animal> animals = giraffes;

This is not valid in C#3, however it should be possible since a sequence of giraffes is a sequence of animals. The projection T -> IEnumerable<T> preserves the 'direction' of the type relationship since Giraffe < Animal and IEnumerable<Giraffe> < IEnumerable<Animal> (note that assignment requires that the type of the left-hand side is at least as wide as the right).

Contra-variance reverses the type relationship:

Action<Animal> printAnimal = a => {System.Console.WriteLine(a.Name)};
Action<Giraffe> printGiraffe = printAnimal;

This is also not legal in C#3, but it should be since any action taking an animal can cope with being passed a Giraffe. However, since Giraffe < Animal and Action<Animal> < Action<Giraffe> the projection has reversed the type relationships. This is legal in C#4.

So to answer the questions in your example:

//the following are neither covariant or contravariant - since there is no projection this is just assignment compatibility
Mammal mammal1 = new Giraffe();
Mammal mammal2 = new Dolphin();

//compare is contravariant with respect to its arguments - 
//the delegate assignment is legal in C#4 but not in C#3
Func<Mammal, Mammal, bool> compare = (m1, m2) => //whatever
Func<Giraffe, Dolphin, bool> c2 = compare;

//always invalid - right hand side must be smaller or equal to left hand side
Mammal mammal1 = new Animal();

//not valid for same reason - animal cannot be assigned to Mammal
Compare(new Animal(), new Dolphin());
Lee
Lee, I of course understand the difference between assignment compatibility and co/contra-covariance. Introducing a layer in between such as a delegate of course explains the issue very clearly. One would assume that Action<Mammal> could be assigned to Action<Animal>, but of course this is wrong and only Action<Mammal> or less can be assigned to Action<Mammal>. Yes this sort of layer in the middle clearly shows contravariance, but only because there is a layer..are we saying that contravariance only happens when a layer such as this is introduced.
Stan R.
i meant to say co/contra-variance... :), anyway I edited my question. Also i do believe that assignment compatibility is covariant in nature, because covariance preserves the assignment compatibility. However in terms of Action<Animal> = Action<Mammal>, this assignment compatibility is not preserved, but actually reversed..which is contravariance. Even tho Action<Animal> = Action<Mammal> is still assignment compatible (but only because declaring it as contravariant it would not be).
Stan R.
+6  A: 

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.

Alexandra Rusina
@Alexandra. thank you. Your last example is most telling.Were it the other way around a method that took Employee, you would not be able to assign it to an Action<Person>. This would break the rules of contravariant parameters. So what I guess this leads me to is that this is "contravariant" behavior specific for Generic and Interface types.
Stan R.
+2  A: 
Dave