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.