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

时间:2012-10-28 07:18:55

标签: c# c#-4.0 ienumerable covariance

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

IEnumerable<object> ie = new List<string>();

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

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

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

2 个答案:

答案 0 :(得分:21)

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

// Invalid
public class Foo<out T>

其次 - 更重要的是对于您给出的示例 - 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];

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

最后一行在类型方面是安全的 - List<Banana>中唯一的值应为nullBanana或{a}的实例引用子类。

现在,为什么类不能协变,即使它们逻辑是......我相信在实现级别引入问题,并且也会非常在编程层面也是限制性的。例如,考虑一下:

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;
    }
}

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

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

答案 1 :(得分:0)

如果我们想讨论向List添加(接受)某些东西(T),我们必须谈谈CONTRAVARIANCE(COVARIANCE不允许接受),所以:

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.

因为我们可以看到VARIANCE既可以用于类也可以用于接口和委托(一般类,不仅仅用于集合)。我认为它可以在.NET的未来版本中实现。