Covariance means that you can use IEnumerable<string> in place where IEnumerable<object> is expected. Contravariance allows you to pass IComparable<object> as an argument of a method taking IComparable<string>.
Assuming that every
Animal has a Name, we can write the
following function that prints names of all animals in some collection:
void PrintAnimals(IEnumerable<Animal> animals) {
for(var animal in animals)
Console.WriteLine(animal.Name);
}
It is important to
note that IEnumerable<Animal> can be only used to read Animal values from the collection (using the Current property of IEnumerator<Animal>). There is no way we could write Animal back to the collection - it is read-only. Now,
if we create a collection of cats, can we use PrintAnimals to print their names?
IEnumerable<Cat> cats = new List<Cat> { new
Cat("Troublemaker") };
PrintAnimals(cats);
If you compile and
run this sample, you'll see that the C# compiler accepts it and the program
runs fine. When calling PrintAnimals, the compiler uses covariance to convert IEnumerable<Cat> to IEnumerable<Animal>. This is correct, because the IEnumerable interface is marked as covariant using the out annotation. When
you run the program, the PrintAnimals method cannot cause anything wrong, because it can only read
animals from the collection. Using a collection of cats as an argument is fine,
because all cats are animals.
Contravariance
Contravariance works the other way than covariance. Let's say we have a method that creates some
cats and compares them using a provided IComparer<Cat> object. In a more realistic example, the method might, for
example, sort the cats:
void CompareCats(IComparer<Cat> comparer) {
var cat1 = new
Cat("Otto");
var cat2 = new
Cat("Troublemaker");
if (comparer.Compare(cat2,
cat1) > 0)
Console.WriteLine("Troublemaker wins!");
}
The comparer object
takes cats as arguments, but it never returns a cat as the result. You could
say that it is a write-only in the way in which it uses the generic type
parameter. Now, thanks to contravariance,
we can create a comparer that can compare animals and use it as an argument to CompareCats:
IComparator<Animal> compareAnimals = new
AnimalSizeComparator();
CompareCats(compareAnimals);
The compiler
accepts this code because the IComparer interface is contravariant
and its generic type parameter is marked with the in annotation. When you run
the program, it also makes sense. The compareAnimals object that we created knows how to compare animals and so it can
certainly also compare two cats. A problem would be if we could read a Cat from IComparer<Cat> (because we couldn't get Cat from IComparer<Animal>!), but that is not possible, because IComparer is write-only.
Source: Tomas
Petricek's blog http://tomasp.net/blog/variance-explained.aspx/
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.