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#?
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With