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