Универсальное различие в C# 4.0

Универсальное Различие в C# 4.0 было реализовано таким способом, которым возможно записать следующее без исключения (который является тем, что произошло бы в C# 3.0):

 List intList = new List();
 List objectList = intList; 

[Нефункциональный пример: См. ответ Jon Skeet]

Я недавно присутствовал на конференции, где Jon Skeet дал превосходный обзор Универсального Различия, но я не уверен, что полностью получаю его - я понимаю значение in и out ключевые слова когда дело доходит до мятежника и ковариантности, но мне любопытно к тому, что происходит негласно.

Что видит CLR, когда этот код выполнен? Разве это неявно преобразовывает List кому: List или это просто создается, в котором мы можем теперь преобразовать между производными типами для порождения типов?

Из интереса, почему это не было представлено в предыдущих версиях и каково основное преимущество - т.е. использование реального мира?

Больше информации об этом сообщении для Универсального Различия (но вопрос чрезвычайно устарело, ища реальную, актуальную информацию),

16
задан Community 23 May 2017 в 10:30
поделиться

3 ответа

Нет, ваш пример не будет работать по трем причинам:

  • Классы (например, 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) .

    20
    ответ дан 30 November 2019 в 16:09
    поделиться

    Из интереса, почему это не было введено в предыдущих версиях

    В первых версиях (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 , даже если он должен «просто работать». Ко- и контравариантность заставляет это «просто работать».

    п.с. Совершенно отстой, что ответ Скита съедает все мои очки репутации. Будь ты проклят, Скит! :-) Похоже, он ответил на это раньше .

    8
    ответ дан 30 November 2019 в 16:09
    поделиться

    Это странный синтаксис. Они эквивалентны:

    >> [1, 2, 3] * 'joiner'
    => "1joiner2joiner3"
    
    >> [1, 2, 3].join 'joiner'
    => "1joiner2joiner3"
    

    , так что в этом случае он объединяет все записи @ target в одну строку, ничего между ними.

    Примечание: если вы делаете что-то вроде [1, 2, 3] * 3 (используя int вместо str ), вы получите три конкатенированные копии массива.

    -121--2681464-

    Он делает то же самое, что:

    ["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;
    

    Это возвращает биты, которые являются ссылкой на строку. Мы уже установили, что они совместимы с объектом без изменений.

    Почему это не было введено ранее? У нас были другие возможности и ограниченный бюджет.

    В чем принципиальная польза? Что преобразования из последовательности строки в последовательность объекта "просто работают".

    15
    ответ дан 30 November 2019 в 16:09
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: