Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's a common programming use of contra-variance?

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.

like image 853
matanc1 Avatar asked Oct 20 '13 08:10

matanc1


People also ask

What is variance in programming?

Variance refers to how subtyping between more complex types relates to subtyping between their components.

What is co variance and Contra variance in generics?

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.

What is covariant in computer science?

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.

What is difference between covariance and Contravariance?

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.


3 Answers

(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.

like image 136
Matthew Watson Avatar answered Sep 28 '22 20:09

Matthew Watson


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.

like image 26
Rob Lyndon Avatar answered Sep 28 '22 22:09

Rob Lyndon


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 . :)

like image 43
Iulian Radulescu Avatar answered Sep 28 '22 22:09

Iulian Radulescu