Ответ 1
Ответы Marc и CodeInChaos довольно хороши, но просто добавьте несколько деталей:
Во-первых, похоже, что вы заинтересованы в том, чтобы узнать о процессе разработки, который мы прошли, чтобы сделать эту функцию. Если да, то я рекомендую вам прочитать мою длинную серию статей, которые я написал при разработке и реализации этой функции. Начните снизу:
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx
Это просто заставить раздражающие роли в выражениях LINQ уйти?
Нет, это не просто избежать выражений Cast<T>
, но это было одним из мотиваторов, которые побуждали нас делать эту функцию. Мы поняли, что будет число всплесков числа "почему я не могу использовать последовательность Жирафов в этом методе, которая принимает последовательность животных?" потому что LINQ поощряет использование типов последовательностей. Мы знали, что сначала хотим добавить ковариацию в IEnumerable<T>
.
Мы фактически рассматривали ковариацию IEnumerable<T>
даже в С# 3, но решили, что было бы странно делать это без введения всей функции для тех, кто может использовать.
Разве это не приведет к таким же проблемам, как с
string[] <: object[]
(дисперсия разбитого массива) в С#?
Он не вводит эту проблему напрямую, потому что компилятор допускает отклонение, когда он известен как тип. Тем не менее, он сохраняет проблему с ошибкой разбитого массива. С ковариацией IEnumerable<string[]>
неявно конвертируется в IEnumerable<object[]>
, поэтому, если у вас есть последовательность строковых массивов, вы можете рассматривать это как последовательность массивов объектов, а затем у вас есть такая же проблема, как и раньше: вы можете попробовать Giraffe в этот строковый массив и получить исключение во время выполнения.
Как было добавление ковариации с точки зрения совместимости?
Осторожно.
Будет ли предыдущий код работать в более поздних версиях .NET или нужна перекомпиляция?
Только один способ узнать. Попробуйте и посмотрите, что не получается!
Часто бывает сложно попытаться скомпилировать код, скомпилированный против .NET X, для работы с .NET Y, если X!= Y, независимо от изменений в системе типов.
Как насчет другого пути?
Тот же ответ.
Возможно ли, что некоторые варианты использования будут теперь отличаться друг от друга?
Совершенно верно. Создание коварианта интерфейса, где оно было инвариантным до этого, является технически "нарушением изменений", поскольку оно может привести к разрыву рабочего кода. Например:
if (x is IEnumerable<Animal>)
ABC();
else if (x is IEnumerable<Turtle>)
DEF();
Когда IE<T>
не ковариантно, этот код выбирает либо ABC, либо DEF, либо нет. Когда он ковариантен, он больше не выбирает DEF.
Или:
class B { public void M(IEnumerable<Turtle> turtles){} }
class D : B { public void M(IEnumerable<Animal> animals){} }
До того, как вы вызвали M в экземпляре D с чередой черепов в качестве аргумента, разрешение перегрузки выбирает B.M, потому что это единственный применимый метод. Если IE является ковариантным, тогда разрешение на перегрузку теперь выбирает DM, потому что оба метода применимы, а применимый метод для более производного класса всегда превосходит применимый метод для менее производного класса, независимо от того, является ли совпадение типа аргумента точным или нет,
Или:
class Weird : IEnumerable<Turtle>, IEnumerable<Banana> { ... }
class B
{
public void M(IEnumerable<Banana> bananas) {}
}
class D : B
{
public void M(IEnumerable<Animal> animals) {}
public void M(IEnumerable<Fruit> fruits) {}
}
Если IE инвариантен, то вызов d.M(weird)
разрешается B.M. Если IE внезапно становится ковариантным, то оба метода D.M применимы, оба лучше, чем метод базового класса, и ни один не лучше другого, поэтому разрешение перегрузки становится неоднозначным и мы сообщаем об ошибке.
Когда мы решили сделать эти нарушения, мы надеялись, что (1) ситуации будут редкими, и (2) когда такие ситуации возникают, почти всегда это происходит потому, что автор класса пытается имитировать ковариацию на языке, который его не имеет. Добавляя ковариацию напрямую, надеюсь, когда код "ломается" при перекомпиляции, автор может просто удалить сумасшедший механизм, пытающийся смоделировать существующую функцию.