Простейшим способом понять, почему это не разрешено, является следующий пример:
abstract class Fruit
{
}
class Apple : Fruit
{
}
class Banana : Fruit
{
}
// This should intuitively compile right? Cause an Apple is Fruit.
List<Fruit> fruits = new List<Apple>();
// But what if I do this? Adding a Banana to a list of Apples
fruits.Add(new Banana());
Последнее утверждение может разрушить безопасность типов .NET.
Однако массивы разрешаютthis:
Fruit[] fruits = new Apple[10]; // This is perfectly fine
Однако, добавление Banana
в fruits
все равно нарушит безопасность типов, поэтому .NET должен проверять тип при каждой вставке массива и выдавать исключение, если это не такApple
.Это потенциально (небольшое) снижение производительности, но его можно обойти, создав обертку struct
для обоих типов, поскольку эта проверка не выполняется для типов значений (поскольку они не могут наследовать от чего-либо).Сначала я не понимал, почему было принято это решение, но вы часто будете сталкиваться с тем, почему это может быть полезно.Наиболее распространенным является String.Format
, который принимает params object[]
, и любой массив может быть передан в это.
В .NET 4, тем не менее, есть безопасная ковариация / контравариантность типа, которая позволяет вам выполнять некоторые назначения, подобные этим, но только если они доказуемо безопасны.Что доказуемо безопасно?
IEnumerable<Fruit> fruits = new List<Apple>();
Вышеописанное работает в .NET 4, потому что IEnumerable<T>
стало IEnumerable<out T>
.out
означает, что T
может только когда-либо прийти из из fruits
и что нет никакого метода для IEnumerable<out T>
, который когда-либо принимает T
в качестве параметратаким образом, вы никогда не сможете неправильно передать Banana
в IEnumerable<Fruit>
.
Контравариантность во многом одна и та же, но я всегда забываю точные детали о ней.Неудивительно, что для этого теперь есть ключевое слово in
для параметров типа.