Why classes that implement variant interfaces remain invariant?

天涯浪子 提交于 2019-11-27 21:27:50

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 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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!