15

C# 4.0 has extended the co and contravariance further for generic types and interfaces. Some interfaces (like IEnumerable<T>) are covariants, so I can do things like:

IEnumerable<object> ie = new List<string>();

but what about this line? I got a compile-time error

List<Object> list = new List<String>();
//Cannot implicitly convert type List<string>' to List<object>'

I mean, if List<T> implement IEnumerable<T> why List<T> is still invariant? Is out there a good counterexample that explain why this should not be allowed in C#?

Tomas Ramirez Sarduy
  • 17,294
  • 8
  • 69
  • 85

2 Answers2

24

Firstly, classes are always invariant in C#. You can't declare a class like this:

// Invalid
public class Foo<out T>

Secondly - and more importantly for the example you've given - List<T> couldn't be declared to be covariant or contravariant in T anyway, as it has members both accepting and returning values of type T.

Imagine if it were covariant. Then you could write this (for the obvious Fruit class hierarchy):

List<Banana> bunchOfBananas = new List<Banana>();
// This would be valid if List<T> were covariant in T
List<Fruit> fruitBowl = bunchOfBananas;
fruitBowl.Add(new Apple());
Banana banana = bunchOfBananas[0];

What would you expect that last line to do? Fundamentally, you shouldn't be able to add an Apple reference to an object whose actual execution-time type is List<Banana>. If you add an apple to a bunch of bananas, it falls off. Believe me, I've tried.

The last line should be safe in terms of types - the only values within a List<Banana> should be null or references to instances of Banana or a subclass.

Now as for why classes can't be covariant even when they could logically be... I believe that introduces problems at the implementation level, and would also be very restrictive at the programming level as well. For example, consider this:

public class Foo<out T> // Imagine if this were valid
{
    private T value;

    public T Value { get { return value; } }

    public Foo(T value)
    {
        this.value = value;
    }
}

That would still probably have to be invalid - the variable is still writable, meaning it counts as an "in" slot. You'd have to make every variable of type T read-only... and that's just for starters. I strongly suspect that there would be deeper problems.

In terms of pure pragmatism, the CLR has supported delegate and interface variance from v2 - C# 4 just introduced the syntax to expose the feature. I don't believe the CLR has ever supported generic class variance.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Jon, how does this account for having a list of Fruit that you would add instances of class that inherit from Fruit? For example, `List fruitBowl = new List(); fruitBowl.Add(new Apple()); fruitBowl.add(new Banana());` I've done this in the past and it always behaves as expected. Why doesn't the CLR look at the type of `fruitBowl`? Is it because you are setting the value of your `fruitBowl` to a list of Bananas first which is covariant to a list of `Fruit` and *then* trying to add an invariant `Apple` to that same collection? – elucid8 May 01 '13 at 12:51
  • @elucid8: It's absolutely fine to have a `List` and add arbitrary fruit to it. It's just not correct to say that a `List` is a `List` - precisely *because* you can't add arbitrary fruit to a `List`. I'm not sure I understand your question. Perhaps ask in a new post, if this doesn't help? (Comment threads aren't ideal for this.) – Jon Skeet May 01 '13 at 13:02
  • I don't agree with Jon, I think he is mistaken in what if we are discussing COVARIANCE then we have to use a method of the LIST what returns T. But Jon is about method ADD which ACCEPTS T so we need to talk about CONTRAVARIANCE and in this case all things will go in right way – Maxim Q Jan 08 '16 at 20:04
  • So then you think it's impossible to ever get a value out of a list? That's...not the case. – Servy Jan 08 '16 at 20:08
  • Servy, yes. But we are discussing here not collections, but CLASS VARIANCE! – Maxim Q Jan 08 '16 at 21:27
  • @MaximQ: And the problem is precisely because there *is* an `Add` method, accepting values into it. We're discussing covariance because the OP wants to be able to assign a value of type `List` to a variable of type `List`. That could only work if `List` were covariant in `T`. But it can't logically be covariant in `T` because the `Add` method (and similar methods accepting a `T`) exist. – Jon Skeet Jan 09 '16 at 07:32
0

If we want to discuss adding (accepting) something (T) to List, we must talk about CONTRAVARIANCE (COVARIANCE doesn't allow accepting), so:

List<Fruit> bunchOfFruits = new List<Fruit>();
// This would be valid if List<T> were contravariant in T
List<Banana> bunchOfBananas = bunchOfFruits;
bunchOfBananas.Add(new Apple()); // not possible! We have compile error, coz in spite of Apple is a Fruit it is not ancestor of Banana. No logical mistakes.
bunchOfBananas.Add(new BigBanana()); // possible coz of rules of C#! we deal with a descendant of Banana
bunchOfBananas.Add(new Fruit()); // possible, coz CONTRAVARIANCE of List. We deal with the ancestor of Banana. No logical mistakes.

So as we can see VARIANCE should work either for classes too as for interfaces and delegates (classes in general, not only for collections). And I think it can be implemented in future versions of .NET.

Maxim Q
  • 19
  • 1
  • 2