Ответы Марка и CodeInChaos довольно хорошие, но просто добавим еще несколько деталей:
Во-первых, звучит так, как будто вы заинтересованы в изучении процесса проектирования, через который мы прошли, чтобы создать эту функцию. Если так, то я призываю вас прочитать мою большую серию статей, которые я написал при разработке и реализации этой функции. Начните снизу:
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx
Это просто чтобы убрать надоедливые приведения в выражениях LINQ?
Нет, это не просто , чтобы избежать Cast<T>
выражений, но это было одним из мотиваторов, которые побудили нас сделать эту функцию. Мы поняли, что в числе «будет увеличение количества« почему я не могу использовать последовательность жирафов в этом методе, который принимает последовательность животных? » вопросы, потому что LINQ поощряет использование типов последовательности. Мы знали, что сначала хотим добавить ковариацию к IEnumerable<T>
.
На самом деле мы рассматривали вопрос о том, чтобы сделать IEnumerable<T>
ковариантным даже в C # 3, но решили, что было бы странно делать это, не вводя всю функциональность для всех.
Не приведет ли это к тем же проблемам, что и с string[] <: object[]
(разбитый массив) в C #?
Эта проблема напрямую не возникает, поскольку компилятор допускает дисперсию только тогда, когда известно, что он безопасен для типов. Тем не менее, сохраняет проблему дисперсии битого массива. С ковариацией, IEnumerable<string[]>
неявно преобразуется в IEnumerable<object[]>
, поэтому, если у вас есть последовательность строковых массивов, вы можете рассматривать это как последовательность объектных массивов, и тогда у вас будет та же проблема, что и раньше: вы можете попытаться поместить Жираф в этот строковый массив и получить исключение во время выполнения.
Как было добавлено ковариация с точки зрения совместимости?
Тщательно.
Будет ли более ранний код работать на более поздних версиях .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) когда такие ситуации возникают, почти всегда это происходит потому, что автор класса пытается симулировать ковариацию на языке, который не имеет его. Добавляя ковариацию напрямую, мы надеемся, что когда код «сломается» при перекомпиляции, автор может просто удалить сумасшедший механизм, пытаясь смоделировать существующую функцию.