Why classes that implement variant interfaces remain invariant?

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.

Leave a Comment