通用协方差编译时安全检查

Generic covariance compile time safety checks

为什么 List<T>T 上不协变,而 IEnumerable<T> T 上协变的原因通常是用这样的例子来说明。

给出以下 类:

public class Fruit
{
}

public class Apple : Fruit
{
}

public class Banana : Fruit
{
}

允许以下内容:

public void Permitted()
{
    IEnumerable<Fruit> bananas = new List<Banana>
    {
        new Banana(),
        new Banana(),
        new Banana(),
    };

    foreach (Fruit banana in bananas)
    {
        // This is all good, because a banana "is a" fruit and
        // we can treat it as such.
    }
}

不允许以下内容:

public void Disallowed()
{
    // Compiler rejects this!
    List<Fruit> bananas = new List<Banana>
    {
        new Banana(),
        new Banana(),
        new Banana(),
    };

    // ...Otherwise we can add an apple to a list containing bananas
    bananas.Add(new Apple());
}

但是,我们仍然可以通过执行以下操作来实现此目的:

public void Loophole()
{
    // Compiler is happy again
    IEnumerable<Fruit> bananas = new List<Banana>
    {
        new Banana(),
        new Banana(),
        new Banana(),
    };

    // ...And now we can add an apple to a list of bananas
    bananas.ToList().Add(new Apple());
}

当然我们也可以这样做:

public void AlsoAllowed()
{
    var fruit = new List<Fruit>();
    fruit.Add(new Apple());
    fruit.Add(new Banana());
}

List<T> 不是协变的常见论点(根据我的理解)是这样做将允许我们将任意基础对象添加到包含派生对象的集合中。也许这是过于简单化了,但这不就是上面的例子所做的吗?

当您执行 bananas.ToList().Add(new Apple()) 时,bananas.ToList() 会创建一个 List<Fruit>。这是一个列表类型,可以包含任何类型的水果。 new Apple() 可以添加到该列表的事实是有道理的。

bananas 的类型为 List<Banana>,这是一个只能包含香蕉的列表类型。无法将 new Apple() 添加到此列表,并且您的示例 不会 new Apple() 添加到此列表。您的示例创建了一个更宽容的列表,并添加到该列表中,但保留原始列表不变。