I've read the following post on contra variance and Lasse V. Karlsen's answer:
Understanding Covariant and Contravariant interfaces in C#
Even though I understand the concept, I don't get why it's useful.
For example, why would anyone make a read only list (Like in the post: List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
)
I also know that overriding methods' parameters can be contra-variance (conceptually. This isn't in use in C#, Java and C++ as far as I know). What examples are there in which this makes sense?
I'd appreciate some simple real world examples.
Variance refers to how subtyping between more complex types relates to subtyping between their components.
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.
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.
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.
(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.
Covariance is useful for read-only (out) repositories; contravariance for write-only (in) repositories.
public interface IReadRepository<out TVehicle>
{
TVehicle GetItem(Guid id);
}
public interface IWriteRepository<in TVehicle>
{
void AddItem(TVehicle vehicle);
}
This way, an instance of IReadRepository<Car>
is also an instance of IReadRepository<Vehicle>
because if you get a car out of the repository, it is also a vehicle; however, an instance of IWriteRepository<Vehicle>
is also an instance of IWriteRepository<Car>
, because if you can add a vehicle to the repository, you can therefore write a car to the repository.
This explains the reasoning behind the out
and in
keywords for covariance and contravariance.
As for why you might want to make this separation (using separate read-only and write-only interfaces, which would most probably be implemented by the same concrete class), this helps you keep a clean separation between commands (write operations) and queries (read operations), which will probably have different requirements regarding performance, consistency and availability, and therefore need different strategies in your codebase.
If you don't really grasp the concept of contravariance then problems like that can (and will) occur.
Let us build the simplest example :
public class Human
{
virtual public void DisplayLanguage() { Console.WriteLine("I do speak a language"); }
}
public class Asian : Human
{
override public void DisplayLanguage() { Console.WriteLine("I speak chinesse"); }
}
public class European : Human
{
override public void DisplayLanguage() { Console.WriteLine("I speak romanian"); }
}
Ok here is an example of contra variance
public class test
{
static void ContraMethod(Human h)
{
h.DisplayLanguage();
}
static void ContraForInterfaces(IEnumerable<Human> humans)
{
foreach (European euro in humans)
{
euro.DisplayLanguage();
}
}
static void Main()
{
European euro = new European();
test.ContraMethod(euro);
List<European> euroList = new List<European>() { new European(), new European(), new European() };
test.ContraForInterfaces(euroList);
}
}
Before .Net 4.0 the method ContraForInterfaces was not allowed (so they actually fixed a BUG :) ).
Ok , now what is the meaning of the ContraForInterfaces method ? It is simple , once you made a list of europeans, the objects within are ALWAYS europeans, even if you pass them to a method that takes IEnumerable<>. In this way you will allways call the right method for your object . Covariance and ContraVariance is just parameter polymorphism (and nothing more) applied now to delegates and Interfaces also . :)
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