Универсальное Различие в C# 4.0 было реализовано таким способом, которым возможно записать следующее без исключения (который является тем, что произошло бы в C# 3.0):
List intList = new List();
List
[Нефункциональный пример: См. ответ Jon Skeet]
Я недавно присутствовал на конференции, где Jon Skeet дал превосходный обзор Универсального Различия, но я не уверен, что полностью получаю его - я понимаю значение in
и out
ключевые слова когда дело доходит до мятежника и ковариантности, но мне любопытно к тому, что происходит негласно.
Что видит CLR, когда этот код выполнен? Разве это неявно преобразовывает List
кому: List
или это просто создается, в котором мы можем теперь преобразовать между производными типами для порождения типов?
Из интереса, почему это не было представлено в предыдущих версиях и каково основное преимущество - т.е. использование реального мира?
Больше информации об этом сообщении для Универсального Различия (но вопрос чрезвычайно устарело, ища реальную, актуальную информацию),
Нет, ваш пример не будет работать по трем причинам:
List
) инвариантны; только делегаты и интерфейсы являются вариантами IEnumerable
к IEnumerable
, например (код не компилируется как в C # 3.0, так и в 4.0 - исключений нет)
Итак, это будет работать:
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;
CLR просто использует ссылку без изменений - новые объекты не создаются. Итак, если вы вызвали objects.GetType ()
, вы все равно получите List
.
Я считаю, что он не был представлен ранее, потому что разработчикам языка еще нужно было проработать детали того, как его раскрыть - он был в CLR с v2.
Преимущества такие же, как и в других случаях, когда вы хотите использовать один тип как другой. Чтобы использовать тот же пример, который я использовал в прошлую субботу, если у вас есть что-то, реализующее IComparer
для сравнения фигур по площади, безумие, что вы не можете использовать это для сортировки List < Circle>
- если он может сравнивать любые две формы, он, безусловно, может сравнивать любые два круга. В C # 4 будет контравариантное преобразование из IComparer
в IComparer
, чтобы вы могли вызывать circle.Sort (areaComparer)
.
Из интереса, почему это не было введено в предыдущих версиях
В первых версиях (1.x) .NET вообще не было универсальных шаблонов, поэтому универсальная дисперсия была далека.
Следует отметить, что во всех версиях .NET существует ковариация массивов. К сожалению, это небезопасная ковариация:
Apple[] apples = new [] { apple1, apple2 };
Fruit[] fruit = apples;
fruit[1] = new Orange(); // Oh snap! Runtime exception! Can't store an orange in an array of apples!
Ко- и контравариантность в C # 4 безопасна и предотвращает эту проблему.
в чем основное преимущество - то есть реальное использование мира?
Много раз в коде, вы вызываете API, ожидает усиленный тип Base (например, IEnumerable
) но все, что у вас есть, - это расширенный тип Derived (например, IEnumerable
).
В C # 2 и C # 3 вам нужно будет вручную преобразовать в IEnumerable
, даже если он должен «просто работать». Ко- и контравариантность заставляет это «просто работать».
п.с. Совершенно отстой, что ответ Скита съедает все мои очки репутации. Будь ты проклят, Скит! :-) Похоже, он ответил на это раньше .
Это странный синтаксис. Они эквивалентны:
>> [1, 2, 3] * 'joiner'
=> "1joiner2joiner3"
>> [1, 2, 3].join 'joiner'
=> "1joiner2joiner3"
, так что в этом случае он объединяет все записи @ target
в одну строку, ничего между ними.
Примечание: если вы делаете что-то вроде [1, 2, 3] * 3
(используя int
вместо str
), вы получите три конкатенированные копии массива.
Он делает то же самое, что:
["foo","bar","baz"].join
http://ruby-doc.org/core/classes/Array.html#M002210
Согласно предложению Z.E.D., вы бы использовали его, если хотите запутать людей и сделать ваш код более подверженным ошибке.
-121--2681465-Несколько дополнительных мыслей.
Что видит CLR при выполнении этого кода
Как правильно отметили Джон и другие, мы не делаем расхождений в классах, только интерфейсы и делегаты. Так что в вашем примере CLR ничего не видит; этот код не компилируется. Если принудительно выполнить компиляцию путем вставки достаточного количества слепков, во время выполнения происходит сбой с плохим исключением литья.
Теперь, это все еще разумный вопрос, чтобы спросить, как дисперсия работает за кадром, когда он работает. Ответ: причина, по которой мы ограничиваем это аргументами ссылочного типа, которые параметризуют типы интерфейса и делегата так, что ничего не происходит за кадром. Когда вы говорите
object x = "hello";
, что происходит за кадром, ссылка на строку вставляется в переменную объекта типа без изменения . Биты, составляющие ссылку на строку, являются допустимыми битами, чтобы быть ссылкой на объект, поэтому здесь ничего не должно происходить. CLR просто прекращает думать, что эти биты относятся к строке, и начинает думать, что они относятся к объекту.
Когда вы говорите:
IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = e1;
То же самое. Ничего не происходит. Биты, которые образуют ссылку на перечислитель строк, совпадают с битами, которые ссылаются на перечислитель объектов. Есть несколько больше магии, которая вступает в игру, когда вы делаете кастинг, скажем:
IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = (IEnumerator<object>)(object)e1;
Теперь CLR должен генерировать проверку, что e1 действительно реализует этот интерфейс, и эта проверка должна быть умной в распознавании дисперсии.
Но причина, по которой мы можем уйти с различных интерфейсов просто не-op преобразования является , потому что регулярное назначение совместимости является таким образом. Что вы собираетесь использовать для e2?
object z = e2.Current;
Это возвращает биты, которые являются ссылкой на строку. Мы уже установили, что они совместимы с объектом без изменений.
Почему это не было введено ранее? У нас были другие возможности и ограниченный бюджет.
В чем принципиальная польза? Что преобразования из последовательности строки в последовательность объекта "просто работают".