Covariance and Contravariance Real World Example

Covariance and contravariance real world example

Let's say you have a class Person and a class that derives from it, Teacher. You have some operations that take an IEnumerable<Person> as the argument. In your School class you have a method that returns an IEnumerable<Teacher>. Covariance allows you to directly use that result for the methods that take an IEnumerable<Person>, substituting a more derived type for a less derived (more generic) type. Contravariance, counter-intuitively, allows you to use a more generic type, where a more derived type is specified.

See also Covariance and Contravariance in Generics on MSDN.

Classes:

public class Person 
{
public string Name { get; set; }
}

public class Teacher : Person { }

public class MailingList
{
public void Add(IEnumerable<out Person> people) { ... }
}

public class School
{
public IEnumerable<Teacher> GetTeachers() { ... }
}

public class PersonNameComparer : IComparer<Person>
{
public int Compare(Person a, Person b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : Compare(a,b);
}

private int Compare(string a, string b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.CompareTo(b);
}
}

Usage:

var teachers = school.GetTeachers();
var mailingList = new MailingList();

// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);

// the Set<T> constructor uses a contravariant interface, IComparer<in T>,
// we can use a more generic type than required.
// See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());

Examples of good, real-life use-cases for covariance and contravariance in C# 4.0?

The only "real world" use of variance that I've run into in my actual writing-programs-for-customers (as opposed to writing compiler test cases or articles or whatever) is I now can pass an IEnumerable<TypeParameterDefinedInSource> to a method that expects an IEnumerable<TypeSymbol> when I write code analysis tools in C#.

It's really not a "save the day" sort of feature. It's more of a "it works the way I expect it to" feature.

Simple examples of co and contravariance


Could someone provide me simple C# examples of convariance, contravariance, invariance and contra-invariance (if such thing exists).

I have no idea what "contra-invariance" means. The rest are easy.

Here's an example of covariance:

void FeedTheAnimals(IEnumerable<Animal> animals) 
{
foreach(Animal animal in animals)
animal.Feed();
}
...
List<Giraffe> giraffes = ...;
FeedTheAnimals(giraffes);

The IEnumerable<T> interface is covariant. The fact that Giraffe is convertible to Animal implies that IEnumerable<Giraffe> is convertible to IEnumerable<Animal>. Since List<Giraffe> implements IEnumerable<Giraffe> this code succeeds in C# 4; it would have failed in C# 3 because covariance on IEnumerable<T> did not work in C# 3.

This should make sense. A sequence of Giraffes can be treated as a sequence of Animals.

Here's an example of contravariance:

void DoSomethingToAFrog(Action<Frog> action, Frog frog)
{
action(frog);
}
...
Action<Animal> feed = animal=>{animal.Feed();}
DoSomethingToAFrog(feed, new Frog());

The Action<T> delegate is contravariant. The fact that Frog is convertible to Animal implies that Action<Animal> is convertible to Action<Frog>. Notice how this relationship is the opposite direction of the covariant one; that's why it is "contra" variant. Because of the convertibility, this code succeeds; it would have failed in C# 3.

This should make sense. The action can take any Animal; we need an action that can take any Frog, and an action that can take any Animal surely can also take any Frog.

An example of invariance:

void ReadAndWrite(IList<Mammal> mammals)
{
Mammal mammal = mammals[0];
mammals[0] = new Tiger();
}

Can we pass an IList<Giraffe> to this thing? No, because someone is going to write a Tiger into it, and a Tiger cannot be in a list of Giraffes. Can we pass an IList<Animal> into this thing? No, because we are going to read a Mammal out of it, and a list of Animals might contain a Frog. IList<T> is invariant. It can only be used as what it actually is.

For some additional thoughts on the design of this feature, see my series of articles on how we designed and built it.

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

Scala contravariance - real life example

In my opinion, the two most simple examples after Function are ordering and equality. However, the first is not contra-variant in Scala's standard library, and the second doesn't even exist in it. So, I'm going to use Scalaz equivalents: Order and Equal.

Next, I need some class hierarchy, preferably one which is familiar and, of course, it both concepts above must make sense for it. If Scala had a Number superclass of all numeric types, that would have been perfect. Unfortunately, it has no such thing.

So I'm going to try to make the examples with collections. To make it simple, let's just consider Seq[Int] and List[Int]. It should be clear that List[Int] is a subtype of Seq[Int], ie, List[Int] <: Seq[Int].

So, what can we do with it? First, let's write something that compares two lists:

def smaller(a: List[Int], b: List[Int])(implicit ord: Order[List[Int]]) =
if (ord.order(a,b) == LT) a else b

Now I'm going to write an implicit Order for Seq[Int]:

implicit val seqOrder = new Order[Seq[Int]] { 
def order(a: Seq[Int], b: Seq[Int]) =
if (a.size < b.size) LT
else if (b.size < a.size) GT
else EQ
}

With these definitions, I can now do something like this:

scala> smaller(List(1), List(1, 2, 3))
res0: List[Int] = List(1)

Note that I'm asking for an Order[List[Int]], but I'm passing a Order[Seq[Int]]. This means that Order[Seq[Int]] <: Order[List[Int]]. Given that Seq[Int] >: List[Int], this is only possible because of contra-variance.

The next question is: does it make any sense?

Let's consider smaller again. I want to compare two lists of integers. Naturally, anything that compares two lists is acceptable, but what's the logic of something that compares two Seq[Int] being acceptable?

Note in the definition of seqOrder how the things being compared becomes parameters to it. Obviously, a List[Int] can be a parameter to something expecting a Seq[Int]. From that follows that a something that compares Seq[Int] is acceptable in place of something that compares List[Int]: they both can be used with the same parameters.

What about the reverse? Let's say I had a method that only compared :: (list's cons), which, together with Nil, is a subtype of List. I obviously could not use this, because smaller might well receive a Nil to compare. It follows that an Order[::[Int]] cannot be used instead of Order[List[Int]].

Let's proceed to equality, and write a method for it:

def equalLists(a: List[Int], b: List[Int])(implicit eq: Equal[List[Int]]) = eq.equal(a, b)

Because Order extends Equal, I can use it with the same implicit above:

scala> equalLists(List(4, 5, 6), List(1, 2, 3)) // we are comparing lengths!
res3: Boolean = true

The logic here is the same one. Anything that can tell whether two Seq[Int] are the same can, obviously, also tell whether two List[Int] are the same. From that, it follows that Equal[Seq[Int]] <: Equal[List[Int]], which is true because Equal is contra-variant.

Real world use of Contravariance and Covariance in .net

Have you ever written a foreach? If you have then you have used Covariance so that is the real world usage. You can use foreach on any type which implements IEnumerable. Here is the signature for IEnumerable:

public interface IEnumerable<out T> : IEnumerable
^^
||
// See the above out keyword

That out keyword is for covariance so it applies only to return types.

Have you ever used the IComparable interface, then you have used contravariance. Here is the signature:

public interface IComparer<in T>
^^
||
// See the above in keyword

That in keyword is for contravariance so it applies only to parameter types of the members of the interface.

And if the out and in keywords are missing then it applies to both input parameter and return parameters. That is called invariance.

Covariance and Contravariance - Just different mechanisms for invoking guaranteed base class behavior?


I'm having a struggle understanding these two concepts.

Yes you are. Many people do.

But I think after many videos and SO QA's, I have it distilled down to its simplest form:

You have not.

Covariance means that a sub-type can do what its base-type does.

No. That's the Liskov Substitution Principle.

Contravariance means you can treat a sub-type the same way you would treat its base-type.

No. That's just re-stating what you said for covariance.

The real distillation of covariance and contravariance is:

  • A covariant conversion preserves the direction of another conversion.

  • A contravariant conversion reverses the direction of another conversion.

Dog is convertible to Animal. IEnumerable<Dog> is convertible to IEnumerable<Animal>.
The direction is preserved, so IEnumerable<T> is covariant. IComparable<Animal> is convertible to IComparable<Dog>, which reverses the direction of the conversion, so it is contravariant.

I understand mathematically what covariance means, and so I guess it's the same in compsci.

Just to be clear: mathematicians use "variance" to mean a bunch of different things. The meaning that is common to mathematics and computer science is the category theory definition.

In C# it's just a matter of where and in what ways these two types of relationships are supported?

Mathematically, variance tells you about whether a relation is preserved or reversed by a mapping. If we have the mapping T --> IEnumerable<T> and the relation "is convertible to via identity or reference conversion" then it is the case that in C#, if X relates to Y then IE<X> relates to IE<Y>. The mapping is therefore said to be covariant with respect to the relation.

what is it that these features are trying to accomplish by supporting them?

People frequently requested "I have a method that takes a sequence of animals and I have a sequence of turtles in hand; why do I have to copy the sequence to a new sequence to use the method?" That's a reasonable request, we got it frequently, and we got it a lot more frequently after LINQ made it easier to work with sequences. It's a generally useful feature that we could implement at a reasonable cost, so we implemented it.

still confused about covariance and contravariance & in/out

Both covariance and contravariance in C# 4.0 refer to the ability of using a derived class instead of base class. The in/out keywords are compiler hints to indicate whether or not the type parameters will be used for input and output.

Covariance

Covariance in C# 4.0 is aided by out keyword and it means that a generic type using a derived class of the out type parameter is OK. Hence

IEnumerable<Fruit> fruit = new List<Apple>();

Since Apple is a Fruit, List<Apple> can be safely used as IEnumerable<Fruit>

Contravariance

Contravariance is the in keyword and it denotes input types, usually in delegates. The principle is the same, it means that the delegate can accept more derived class.

public delegate void Func<in T>(T param);

This means that if we have a Func<Fruit>, it can be converted to Func<Apple>.

Func<Fruit> fruitFunc = (fruit)=>{};
Func<Apple> appleFunc = fruitFunc;

Why are they called co/contravariance if they are basically the same thing?

Because even though the principle is the same, safe casting from derived to base, when used on the input types, we can safely cast a less derived type (Func<Fruit>) to a more derived type (Func<Apple>), which makes sense, since any function that takes Fruit, can also take Apple.

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

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.



Related Topics



Leave a reply



Submit