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

本文关键字:安全检查 编译 方差 | 更新日期: 2023-09-27 18:08:03

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()添加到此列表中。您的示例创建了一个更宽容的列表,并对其进行了添加,但不修改原始列表。