Как универсальная ковариация и контравариантность реализованы в C # 4.0?

106

Я не был на PDC 2008, но услышал новости о том, что C # 4.0 объявлен для поддержки универсальной ковариантности и контр-дисперсии. То есть List<string>можно назначить List<object>. Как такое могло быть?

В книге Джона Скита « C # in Depth» объясняется, почему универсальные шаблоны C # не поддерживают ковариацию и контр-дисперсию. Это в основном для написания безопасного кода. Теперь C # 4.0 изменился для их поддержки. Принесет ли это хаос?

Кто-нибудь знает подробности о С # 4.0, может дать какое-то объяснение?

Морган Ченг
источник
Вот хорошая статья, в которой рассказывается о предстоящих реализациях ковариации и контравариантности делегатов и интерфейсов в C # 4.0: Ферма LINQ: ковариация и контравариантность в C # 4.0
CMS
Андерс Норос объясняет эту концепцию в C # 4.0 - Ковариация и противоречие и показывает, что она уже поддерживается сегодня в IL, начиная с .NET 2.0.
Томас Фройденберг,

Ответы:

155

Вариативность будет поддерживаться только безопасным способом - фактически, с использованием уже имеющихся у CLR возможностей. Так что примеры, которые я привожу в книге, попытки использовать a List<Banana>как List<Fruit>(или что бы там ни было) все равно не сработают, но несколько других сценариев будут.

Во-первых, он будет поддерживаться только для интерфейсов и делегатов.

Во-вторых, он требует, чтобы автор интерфейса / делегата украсил параметры типа как in(для контравариантности) или out(для ковариации). Самый очевидный пример - это то, IEnumerable<T>что позволяет вам только «извлекать» из него значения - он не позволяет вам добавлять новые. Что станет IEnumerable<out T>. Это нисколько не вредит безопасности типов, но позволяет, например, возвращать IEnumerable<string>из метода, объявленного для возврата IEnumerable<object>.

Контравариантность сложнее привести конкретные примеры использования интерфейсов, но с делегатом это легко. Учтите Action<T>- это просто представляет метод, который принимает Tпараметр. Было бы неплохо иметь возможность беспрепятственно конвертировать, используя Action<object>как an Action<string>- любой метод, который принимает objectпараметр, будет прекрасен, если stringвместо этого он будет представлен с . Конечно, в C # 2 уже есть ковариация и контравариантность делегатов в некоторой степени, но через фактическое преобразование из одного типа делегата в другой (создание нового экземпляра) - см. P141-144 для примеров. C # 4 сделает это более универсальным и (я полагаю) позволит избежать создания нового экземпляра для преобразования. (Вместо этого это будет ссылочное преобразование.)

Надеюсь, это немного проясняет ситуацию - пожалуйста, дайте мне знать, если это не имеет смысла!

Джон Скит
источник
3
Итак, означает ли это, что если класс объявлен как «List <out T>», тогда он НЕ должен иметь функцию-член, такую ​​как «void Add (T obj)»? Компилятор C # 4.0 сообщит об ошибке, верно?
Морган Ченг
1
Морган: Я так понимаю, да.
Джон Скит,
4
И снова один из ваших ответов здесь, на SO, сразу помог мне улучшить код. Спасибо!
Марк
@ Арк-кун: Да, я в курсе. Следовательно, в том же предложении есть «все равно не сработает». (И я тоже знаю причины.)
Джон Скит
@JonSkeet ли это исправить , что «вы можете использовать только List<Banana>как IList<Fruit>» , как @ Арк-кун сказал? Если да, то как это возможно, хотя параметр типа IList<T>интерфейса не определен как ковариантный (нет out T, а просто T).
gehho
5

Не то чтобы Джон еще не рассказал об этом, но вот несколько ссылок на блоги и видео Эрика Липперта. Он прекрасно объясняет это на примерах.

https://blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/

Видео:

https://www.youtube.com/watch?v=3MQDrKbzvqU

https://www.youtube.com/watch?v=XRIadQaBYlI

https://www.youtube.com/watch?v=St9d2EDZfrg

kemiller2002
источник