Общая дисперсия в 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>
toIEnumerable<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. Полный отстой, что ответ скита съедает все мои очки репутации. Будь ты проклят, скит! :- ) Похоже, он ответил на это раньше, хотя.