Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why classes that implement variant interfaces remain invariant?

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#?

like image 980
Tom Sarduy Avatar asked Oct 28 '12 07:10

Tom Sarduy


People also ask

How do you declare a generic interface as Contravariant?

You can declare a generic type parameter contravariant by using the in keyword. The contravariant type can be used only as a type of method arguments and not as a return type of interface methods. The contravariant type can also be used for generic constraints.

What is variance in C#?

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.

What is covariance and Contravariance 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.


1 Answers

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.

like image 95
Jon Skeet Avatar answered Oct 14 '22 15:10

Jon Skeet