Search code examples
c#covariancecontravarianceinvariants

C# 3.5 Covariance issue?


I've been hearing/reading a lot about covariance issues in C# and I wanted to pose a few questions & scenarios so hopefully I can clear up my confusion on the matter.

Throughout these examples, please assume the following is always defined:

public class Apple : Fruit {}

My first example:

IList<Apple> apples = GetApples();
IList<Fruit> fruits = apples;

This should work, correct? I tested this a couple of times in C# and it compiled fine & ran fine (my tests for my first example were slightly more than this, as I had polymorphic calls that printed stuff to the console).

Second example:

IList<Apple> apples = GetApples();
IList<object> fruits = apples;

In this second example, my understanding is that this should not compile and is the root of the covariance issues being resolved in .NET 4.0. Please correct me if I am wrong. I also am aware that .NET 4.0 does not allow covariance/contravariance between concrete types, only interfaces.

Finally, I'd like to get some definitions. I'm not quite clear of the meaning behind these 3 terms:

  • Covariance
  • Contravariance
  • Invariance (same as invariant?)

As for the last word, I used it a lot in C++ to refer to changes that have rules implied to them. For example, if I have an integer and it is only allowed to have a value between 1 and 10, the "invariance" is that it can only be between 1 and 10. I might be misunderstanding this and I'm also not sure if this definition translates well to C# for this specific discussion.

EDIT

My goal is to understand exactly what the covariance or casting issues are with generic interfaces in C#. The examples I posted are my understanding of where the problem lies. If all examples compile/function fine, please present an example that does reproduce the most common covariance/contravariance/casting issues in C#. I need to know this so I can identify and explain the problem to others.


Solution

  • The IList<T> interface is not defined to be covariant because it supports an Add method which mutates the object.

    Consider the following:

    IList<Apple> apples = GetApples();
    IList<object> fruits = apples;
    fruits.Add(new Banana());
    

    You could now get a Banana from apples, which is certainly not what was intended. Therefore, the IList interface does not support covariance (and it never will), and should cause a compile error.

    You should have the same problem with

    IList<Apple> apples = GetApples();
    IList<Fruit> fruits = apples;
    fruits.Add(new Banana());
    

    so I'm not sure why it compiled for you.

    The IEnumerable<out T> interface can be covariant (and it is in .NET 4.0 and later), because IEnumerable only supports reading elements from a collection.


    The Scala language has a similar concept of covariant and contravariant objects, and the chapter from Programming in Scala that discusses generics should serve as a good introduction to covariance in C# as well.