为什么实现变体接口的类保持不变?

Tom*_*duy 12 c# ienumerable covariance c#-4.0

C#4.0进一步扩展了通用类型和接口的协方差和逆变.有些接口(比如IEnumerable<T>)是协变量,所以我可以这样做:

IEnumerable<object> ie = new List<string>();
Run Code Online (Sandbox Code Playgroud)

但是这条线怎么样?我遇到了编译时错误

List<Object> list = new List<String>();
//Cannot implicitly convert type List<string>' to List<object>'
Run Code Online (Sandbox Code Playgroud)

我的意思是,如果List<T>实现IEnumerable<T>为什么List<T>仍然是不变的?有没有一个很好的反例可以解释为什么在C#中不允许这样做?

Jon*_*eet 21

首先,C#中的类总是不变的.你不能声明这样的类:

// Invalid
public class Foo<out T>
Run Code Online (Sandbox Code Playgroud)

其次 - 更重要的是你给出的例子 - List<T>无论如何都不能被声明为协变或逆变T,因为它有成员接受和返回类型的值T.

想象一下,如果它协变的.然后你可以写这个(对于明显的Fruit类层次结构):

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];
Run Code Online (Sandbox Code Playgroud)

你期望最后一行做什么?从根本上说,您不应该添加Apple对实际执行时类型为的对象的引用List<Banana>.如果你在一堆香蕉中加入一个苹果,就会掉下来.相信我,我试过了.

最后一行在类型方面应该是安全的 - a中的唯一值List<Banana>应该是null或引用实例Banana或子类.

现在至于为什么类不能协变,即使它们在逻辑上可以......我相信在实现级别引入问题,并且在编程级别也会非常严格.例如,考虑一下:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这仍然可能必须是无效的 - 变量仍然是可写的,这意味着它被视为"in"插槽.你必须让每个类型的变量都是T只读的......那只是为了初学者.我强烈怀疑会有更深层次的问题.

就纯粹的实用主义而言,CLR支持v2-C#4的委托和接口差异,刚刚介绍了暴露该功能的语法.我不相信CLR曾经支持泛型类差异.