通用协方差编译时安全检查
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()
添加到此列表。您的示例创建了一个更宽容的列表,并添加到该列表中,但保留原始列表不变。
为什么 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()
添加到此列表。您的示例创建了一个更宽容的列表,并添加到该列表中,但保留原始列表不变。