14

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# 中不允许这样做?

4

2 回答 2

23

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

// Invalid
public class Foo<out T>

其次-对于您给出的示例更重要的是-List<T>无论如何都不能声明为协变或逆变T,因为它具有接受和返回 type 值的成员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>. 如果你把一个苹果加到一堆香蕉里,它就会掉下来。相信我,我已经试过了。

最后一行在类型方面应该是安全的 - 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;
    }
}

那仍然可能必须是无效的——变量仍然是可写的,这意味着它被视为一个“in”槽。您必须将每个类型的变量设置为T只读......这只是为了初学者。我强烈怀疑会有更深层次的问题。

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

于 2012-10-28T07:40:52.690 回答
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 的未来版本中实现。

于 2016-01-09T14:26:36.120 回答