42

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

All samples I've seen so far was just casting some object into System.Object.

Grant Smith
  • 755
  • 2
  • 6
  • 11
  • Someone gave casting an object to `System.Object` as an example of covariance? That's not even right at all. – Dan Tao Jan 12 '11 at 17:50
  • I took that to mean passing some kind of object (with some specific, more derived type) in place of `System.Object`, not necessarily casting `object` to `System.Object`, which would be pointless. – Justin Morgan Jan 13 '11 at 17:26
  • _Variance_ should not be confused with _casting_, they are not the same thing. See: [Difference between covariance and upcasting](http://stackoverflow.com/a/6707697/949681). – Pressacco Apr 08 '16 at 18:36

3 Answers3

89

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/

Eric Lippert
  • 612,321
  • 166
  • 1,175
  • 2,033
  • 2
    It might be worth pointing out in your last example, of course, that you *could* cast a `Giraffe[]` to a `Mammal[]` and pass that in, which would result in a run-time error. – Dan Tao Jan 12 '11 at 17:44
  • 3
    @Dan: Good point. C# supports "broken" array covariance, where some covariant conversions are allowed even though they can cause crashes at runtime. – Eric Lippert Jan 12 '11 at 17:51
  • @Eric: Yeah, I definitely understand the reasoning there (I think): often developers want a "subset" of the interface of a `T[]` (or `IList`), to access elements by index with no intention of writing to the collection. This is why I personally believe quite strongly that there ought to be something like a covariant `IArray` interface with an indexed getter in the BCL. This would allow a `List` to act like an `IList` when it's only being used for indexed access and not its mutable characteristics (i.e., in the same case where a `Giraffe[]` can safely act as a `Mammal[]`). – Dan Tao Jan 12 '11 at 17:59
  • 2
    @Dan: alternatively, an actually-immutable array type, guaranteed to not be writeable after its initialization, would be safely covariant. I'd love to have such a thing. The fact that arrays are a collection of *variables* is frequently vexing; I need a collection of *values*. – Eric Lippert Jan 12 '11 at 18:15
  • Is array covariance why `new ReadOnlyCollection(new Frog[] { new Frog() });` compiles (and works) but `new ReadOnlyCollection(new List { new Frog() });` does not? (In this case, the constructor parameter is `IList`). – Anthony Pegram Jan 12 '11 at 18:19
  • @Anthony: You're probably asking Eric, but I'm pretty sure I can safely say the answer is yes. Take any method that accepts an `IList` and try to pass a `U[]` to it (where `U` derives from `T`); you should find that it *compiles* just fine— *working* is another matter, of course. – Dan Tao Jan 12 '11 at 18:41
  • @Dan, I find that as well. So in Eric's example, I *can* pass `Giraffe[]` to `ReadAndWrite` (and have it fail at runtime on the second line). `List` would fail at compile time. This inconsistent compile-time behavior is unfortunate. – Anthony Pegram Jan 12 '11 at 18:44
  • @Anthony: I seem to remember reading that it is required by the CLR itself. While I agree that it's unfortunate, I personally prefer "broken" array covariance to absolutely no variance at all among collection types that provide indexed access. Of course I *still* believe the best solution is a [covariant indexed interface](http://stackoverflow.com/questions/2110440); but maybe that's just me. – Dan Tao Jan 12 '11 at 19:00
  • @Anthony: Yes, "broken" array covariance explains that. If the resulting object is only used for "read" operations then there's no problem. Any "write" operation involves a runtime type check which could result in an exception. – Eric Lippert Jan 12 '11 at 19:11
  • So does the compiler check the contents of the method to determine whether it's co-/contra-/invariant? What causes this to fail at compile time--is it the presence of "write" operations with covariance and "read" operations with contravariance? – Justin Morgan May 26 '11 at 18:47
  • @Justin: To determine whether an "in" or "out" annotation is correct on an interface type parameter declaration the compiler checks the type parameter constraints, and the signatures and constraints of every method. Property getters and setters are considered to be methods that either return or take the property type, and so on. The bodies are not examined; how could they be? Interface methods don't have bodies! Once the variance annotations are known to be correct, those facts are used in the analysis of the method bodies, just like any other type system fact. – Eric Lippert May 26 '11 at 19:25
  • @Eric - Ahhhh, now *that* makes sense. Thanks. – Justin Morgan May 26 '11 at 19:33
4

Invariance(in this context) is the absence of both co- and contra-variance. So the word contra-invariance doesn't make sense. Any type parameter that's not tagged as either in or out is invariant. This means this type parameter can both be consumed and returned.

A good example of co-variance is IEnumerable<out T> because an IEnumerable<Derived> can be substituted for an IEnumerable<Base>. Or Func<out T> which returns values of type T.
For example an IEnumerable<Dog> can be converted to IEnumerable<Animal> because any Dog is an animal.

For contra-variance you can use any consuming interface or delegate. IComparer<in T> or Action<in T> come to my mind. These never return a variable of type T, only receive it. Wherever you expect to receive a Base you can pass in a Derived.

Thinking of them as input-only or output-only type-parameters makes it easier to understand IMO.

And the word invariants is typically not used together with type variance, but in the context of class or method invariants, and represents a conserved property. See this stackoverflow thread where the differences between invariants and invariance are discussed.

CodesInChaos
  • 100,017
  • 20
  • 197
  • 251
2

If you consider the regular use of generics, you regularly use an interface to handle an object, but the object is an instance of a class - you can't instantiate the interface. Use a simple list of strings as an example.

IList<string> strlist = new List<string>();

I'm sure you're aware of the advantages of using an IList<> rather than directly using a List<>. It allows for inversion of control, and you might decide you don't want to use a List<> any longer, but you want a LinkedList<> instead. The above code works fine because the generic type of both the interface and class is the same: string.

It can get a bit more complicated if you want to make a list of list of string though. Consider this example:

IList<IList<string>> strlists = new List<List<string>>();

This clearly won't compile, because the generic types arguments IList<string> and List<string> are not the same. Even if you declared the outer list as a regular class, like List<IList<string>>, it would not compile - the type arguments do not match.

So here's where covariance can help. Covariance allows you to use a more derived type as the type argument in this expression. If IList<> was made to be covariant, it would simlpy compile and fix the problem. Unfortunately, IList<> is not covariant, but one the interfaces it extends is:

IEnumerable<IList<string>> strlists = new List<List<string>>();

This code now compiles, the type arguments are the same as they were above.

Mark H
  • 13,471
  • 4
  • 26
  • 45