Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the kinds of covariance in C#? (Or, covariance: by example)

Covariance is (roughly) the ability to mirror inheritance of "simple" types in complex types that use them.
E.g. We can always treat an instance of Cat as an instance of Animal. A ComplexType<Cat> may be treated as a ComplexType<Animal>, if ComplexType is covariant.

I'm wondering: what are the "types" of covariance, and how do they relate to C# (are they supported?)
Code examples would be helpful.

For instance, one type is return type covariance, supported by Java, but not C#.

I'm hoping someone with functional programming chops can chime in, too!

like image 482
Cristian Diaconescu Avatar asked Jun 21 '13 09:06

Cristian Diaconescu


People also ask

What is covariance and contravariance in C#?

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.

What is covariance in programming?

Covariance means that a method can return a type that is derived from the delegate's return type. Contra-variance means that a method can take a parameter that is a base of the delegate's parameter type.

What is covariance and contravariance in generics?

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.

What is the difference between corn variance and contravariance?

Conclusion. Contravariance allows you to utilize a less derived type than originally specified, and covariance lets you use a more derived type. In a sense, the reason they were brought to the C# language is so you can extend arrays, delegate types and generic types with polymorphistic features.


1 Answers

Here's what I can think of:

Update

After reading the constructive comments and the ton of articles pointed (and written) by Eric Lippert, I improved the answer:

  • Updated the broken-ness of array covariance
  • Added "pure" delegate variance
  • Added more examples from the BCL
  • Added links to articles that explain the concepts in-depth.
  • Added a whole new section on higher-order function parameter covariance.

Return type covariance:

Available in Java (>= 5)[1] and C++[2], not supported in C# (Eric Lippert explains why not and what you can do about it):

class B {
    B Clone();
}

class D: B {
    D Clone();
}

Interface covariance[3] - supported in C#

The BCL defines the generic IEnumerable interface to be covariant:

IEnumerable<out T> {...}

Thus the following example is valid:

class Animal {}
class Cat : Animal {}

IEnumerable<Cat> cats = ...
IEnumerable<Animal> animals = cats;

Note that an IEnumerable is by definition "read-only" - you can't add elements to it.
Contrast that to the definition of IList<T> which can be modified e.g. using .Add():

public interface IEnumerable<out T> : ...  //covariant - notice the 'out' keyword
public interface IList<T> : ...            //invariant

Delegate covariance by means of method groups [4] - supported in C#

class Animal {}
class Cat : Animal {}

class Prog {
    public delegate Animal AnimalHandler();

    public static Animal GetAnimal(){...}
    public static Cat GetCat(){...}

    AnimalHandler animalHandler = GetAnimal;
    AnimalHandler catHandler = GetCat;        //covariance

}

"Pure" delegate covariance[5 - pre-variance-release article] - supported in C#

The BCL definition of a delegate that takes no parameters and returns something is covariant:

public delegate TResult Func<out TResult>()

This allows the following:

Func<Cat> getCat = () => new Cat();
Func<Animal> getAnimal = getCat; 

Array covariance - supported in C#, in a broken way[6][7]

string[] strArray = new[] {"aa", "bb"};

object[] objArray = strArray;    //covariance: so far, so good
//objArray really is an "alias" for strArray (or a pointer, if you wish)


//i can haz cat?
object cat == new Cat();         //a real cat would object to being... objectified.

//now assign it
objArray[1] = cat                //crash, boom, bang
                                 //throws ArrayTypeMismatchException

And finally - the surprising and somewhat mind-bending
Delegate parameter covariance (yes, that's co-variance) - for higher-order functions.[8]

The BCL definition of the delegate that takes one parameter and returns nothing is contravariant:

public delegate void Action<in T>(T obj)

Bear with me. Let's define a circus animal trainer - he can be told how to train an animal (by giving him an Action that works with that animal).

delegate void Trainer<out T>(Action<T> trainingAction);

We have the trainer definition, let's get a trainer and put him to work.

Trainer<Cat> catTrainer = (catAction) => catAction(new Cat());

Trainer<Animal> animalTrainer = catTrainer;  
// covariant: Animal > Cat => Trainer<Animal> > Trainer<Cat> 

//define a default training method
Action<Animal> trainAnimal = (animal) => 
   { 
   Console.WriteLine("Training " + animal.GetType().Name + " to ignore you... done!"); 
   };

//work it!
animalTrainer(trainAnimal);

The output proves that this works:

Training Cat to ignore you... done!

In order to understand this, a joke is in order.

A linguistics professor was lecturing to his class one day.
"In English," he said, "a double negative forms a positive.
However," he pointed out, "there is no language wherein a double positive can form a negative."

A voice from the back of the room piped up, "Yeah, right."

What's that got to do with covariance?!

Let me attempt a back-of-the-napkin demonstration.

An Action<T> is contravariant, i.e. it "flips" the types' relationship:

A < B => Action<A> > Action<B> (1)

Change A and B above with Action<A> and Action<B> and get:

Action<A> < Action<B> => Action<Action<A>> > Action<Action<B>>  

or (flip both relationships)

Action<A> > Action<B> => Action<Action<A>> < Action<Action<B>> (2)     

Put (1) and (2) together and we have:

,-------------(1)--------------.
 A < B => Action<A> > Action<B> => Action<Action<A>> < Action<Action<B>> (4)
         `-------------------------------(2)----------------------------'

But our Trainer<T> delegate is effectively an Action<Action<T>>:

Trainer<T> == Action<Action<T>> (3)

So we can rewrite (4) as:

A < B => ... => Trainer<A> < Trainer<B> 

- which, by definition, means Trainer is covariant.

In short, applying Action twice we get contra-contra-variance, i.e. the relationship between types is flipped twice (see (4) ), so we're back to covariance.

like image 180
Cristian Diaconescu Avatar answered Oct 04 '22 17:10

Cristian Diaconescu