Общая дисперсия в C# 4.0

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

 List<int> intList = new List<int>();
 List<object> objectList = intList; 

[пример нефункционального: см. ответ Джона Скита]

недавно я присутствовал на конференции, где Джон Скит дал отличный обзор общей дисперсии, но я не уверен, что полностью понимаю это - я понимаю значение in и out ключевые слова, когда дело доходит до contra и co-дисперсии, но мне любопытно, что происходит за кулисами.

что видит среда CLR при выполнении этого кода? это неявное преобразование List<int> to List<object> или он просто встроен в то, что теперь мы можем конвертировать между производными типами в родительские типы?

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

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

3 ответов


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

  • классы (например,List<T>) инвариантны; только делегаты и интерфейсы variant
  • для работы дисперсии интерфейс должен использовать параметр type только в одном направлении (in для контравариации, out для ковариации)
  • типы значений не поддерживаются в качестве аргументов типа для дисперсии-поэтому нет разговора из IEnumerable<int> to IEnumerable<object> для пример

(код не компилируется как в C# 3.0, так и в 4.0 - исключений нет.)

это б работы:

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

среда CLR просто использует ссылку, без изменений - новые объекты не создаются. Так что, если вы позвонили objects.GetType() вы все равно получите List<string>.

Я считаю, что он не был представлен ранее, потому что языковые дизайнеры все еще должны были разработать детали того, как его разоблачить - он был в CLR с тех пор П2.

преимущества такие же, как и в других случаях, когда вы хотите иметь возможность использовать один тип как другой. Чтобы использовать тот же пример, который я использовал в прошлую субботу, если у вас есть что-то реализует IComparer<Shape> сравнивать фигуры по площади, это безумие, что вы не можете использовать это для сортировки List<Circle> - если он может сравнить любые две фигуры, он, безусловно, может сравнить любые два круга. Начиная с C# 4, было бы контравариантное преобразование из IComparer<Shape> to IComparer<Circle> так что вы можете позвонить circles.Sort(areaComparer).


несколько дополнительных мыслей.

что видит среда CLR при выполнении этого кода

Как правильно отметили Джон и другие, мы не делаем различий в классах, только интерфейсы и делегаты. Таким образом, в вашем примере среда CLR ничего не видит; этот код не компилируется. Если вы принудительно скомпилируете его, вставив достаточное количество приведений, он аварийно завершает работу во время выполнения с плохим исключением приведения.

теперь, это все еще разумный вопрос, чтобы спросить, как отклонение работает за кулисами, когда работает. Ответ: причина, по которой мы ограничиваем это ссылочными аргументами типа, которые параметризуют интерфейс и типы делегатов, такова, что ничего происходит за кулисами. Когда вы говорите

object x = "hello";

то, что происходит за кулисами, - это ссылка на строку, застрявшую в переменной типа object без изменений. Биты, составляющие ссылка на строку юридические бит ссылка объект, поэтому здесь ничего не должно произойти. Среда 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;

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

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

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


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

первые версии (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!

co-и contra-дисперсия в C# 4 безопасна и предотвращает эту проблему.

в чем основное преимущество-ie реальный мировое использование?

много раз в коде вы вызываете API, ожидающий усиленный тип базы (например,IEnumerable<Base>) но все, что у вас есть, это усиленный тип производного (например,IEnumerable<Derived>).

в C# 2 и C# 3 вам нужно будет вручную преобразовать в IEnumerable<Base>, хотя он должен "просто работать". Co-и contra-variance делают его "просто работать".

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