(I think this question is more about Covariance rather than Contravariance, since the example quoted is to do with Covariance.)
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Out of context, this is a bit misleading. In the example you quoted, the author intended to convey the idea that technically, if the List<Fish>
was in reality referencing a List<Animal>
it would be safe to add a Fish
to it.
But of course, that would also let you add a Cow
to it - which is clearly wrong.
Therefore the compiler does not allow you to assign a List<Animal>
reference to a List<Fish>
reference.
So when would this actually be safe - and useful?
It is a safe assignment if the collection cannot be modified. In C#, an IEnumerable<T>
can represent an unmodifiable collection.
So you can do this safely:
IEnumerable<Animal> animals = GetAccessToFishes(); // for some reason, returns List<Animal>
because there is no possibility to add a non-Fish to animals
. It has no methods to allow you to do so.
So when would this be useful?
This is useful whenever you want to access some common method or property of a collection which can contain items of one or more types derived from a base class.
For example, you might have a hierarchy representing different categories of stock for a supermarket.
Let's say that the base class, StockItem
, has a property double SalePrice
.
Let's also say you have a method, Shopper.Basket()
which returns an IEnumerable<StockItem>
which represents the items that a shopper has in their basket. The items in the basket may be of any concrete kind derived from StockItem
.
In that case you can add the prices of all the items in the basket (I've written this longhand without using Linq to make clear what is happening. Real code would use IEnumerable.Sum()
of course):
IEnumerable<StockItem> itemsInBasket = shopper.Basket;
double totalCost = 0.0;
foreach (var item in itemsInBasket)
totalCost += item.SalePrice;
Contravariance
An example use of contravariance is when you want to apply some action to an item or collection of items via a base class type, even though you have a derived type.
For example, you could have a method which applied an action to each item in a sequence of StockItem
like so:
void ApplyToStockItems(IEnumerable<StockItem> items, Action<StockItem> action)
{
foreach (var item in items)
action(item);
}
Using the StockItem
example, let's suppose it has a Print()
method which you can use to print it onto a till receipt. You could call then use it like this:
Action<StockItem> printItem = item => { item.Print(); }
ApplyToStockItems(shopper.Basket, printItem);
In this example, the types of the items in the basket might be Fruit
, Electronics
, Clothing
and so on. But because they all derive from StockItem
, the code works with all of them.
Hopefully the utility of this kind of code is clear! This is very similar to the way that a lot of the methods in Linq work.