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!
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.
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.
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.
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.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With