IEnumerable<T>
является ко-вариантом, но не поддерживает тип значения, только ссылочный тип. Приведенный ниже простой код успешно скомпилирован:
IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;
Но переход от string
к int
получит скомпилированную ошибку:
IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;
Причина объясняется в MSDN :
Дисперсия применяется только к ссылочным типам; если вы указываете тип значения для параметра типа варианта, этот параметр типа является инвариантным для результирующего составного типа.
Я искал и обнаружил, что в некоторых вопросах упоминается причина - бокс между типом значения и ссылочным типом . Но это все еще не проясняет мой разум, почему бокс является причиной?
Может ли кто-нибудь дать простое и подробное объяснение, почему ковариация и контравариантность не поддерживают тип значения и как бокс влияет на это?
c#
.net
c#-4.0
covariance
contravariance
cuongle
источник
источник
Ответы:
В основном, дисперсия применяется, когда CLR может гарантировать, что ему не нужно вносить какие-либо репрезентативные изменения в значения. Ссылки выглядят одинаково - так что вы можете использовать
IEnumerable<string>
какIEnumerable<object>
без каких-либо изменений в представлении; самому нативному коду вообще не нужно знать, что вы делаете со значениями, если инфраструктура гарантирует, что он определенно будет действительным.Для типов значений, которые не работают - лечить
IEnumerable<int>
какIEnumerable<object>
код с использованием последовательности должен знать , следует ли выполнять преобразование бокса или нет.Возможно, вы захотите прочитать сообщение Эрика Липперта в блоге о представительстве и идентичности, чтобы узнать больше об этой теме в целом.
РЕДАКТИРОВАТЬ: Перечитав сообщение в блоге Эрика самостоятельно, это по крайней мере так же, как личность, как представление, хотя оба связаны. В частности:
источник
int
не является подтипомobject
. Тот факт, что требуется изменение представительства, является лишь следствием этого.Int32
имеет две репрезентативные формы: «в штучной упаковке» и «без штучной упаковки». Компилятор должен вставить код для преобразования из одной формы в другую, хотя обычно он невидим на уровне исходного кода. В сущности, подтипом системы рассматривается только «коробочная» формаobject
, но компилятор автоматически справляется с этим всякий раз, когда тип значения назначается совместимому интерфейсу или чему-либо типаobject
.Возможно, это будет легче понять, если вы подумаете о базовом представлении (даже если это действительно детали реализации). Вот коллекция строк:
Вы можете думать о том, чтобы
strings
иметь следующее представление:Это коллекция из трех элементов, каждый из которых является ссылкой на строку. Вы можете привести это к коллекции объектов:
По сути, это то же представление, за исключением того, что теперь ссылки являются ссылками на объекты:
Представление то же самое. Ссылки просто по-разному трактуются; Вы больше не можете получить доступ к
string.Length
собственности, но вы все равно можете позвонитьobject.GetHashCode()
. Сравните это с коллекцией целых:Чтобы преобразовать это
IEnumerable<object>
в данные, необходимо преобразовать их в боксы:Это преобразование требует больше, чем приведение.
источник
this
подразумевается структура, чьи поля перекрывают поля объекта кучи, в котором она хранится, а не ссылаются на объект, который их содержит. Не существует чистого способа для экземпляра типа в штучной упаковке получить ссылку на включающий объект кучи.Я думаю, что все начинается с определения
LSP
(принцип подстановки Лискова), который гласит:Но типы значений, например,
int
не могут быть замененыobject
вC#
. Доказать это очень просто:Это возвращает,
false
даже если мы назначаем ту же «ссылку» на объект.источник
int
это не подтип,object
поэтому этот принцип не применяется. Ваше «доказательство» опирается на промежуточное представлениеInteger
, которое является подтипомobject
и для которого язык имеет неявное преобразование (object obj1=myInt;
фактически расширяется доobject obj1=new Integer(myInt)
;).int
это не подтипobject
. Более того, LSP не применяется потомуmyInt
, чтоobj1
иobj2
ссылаются на три разных объекта: одинint
и два (скрытые)Integer
.int
Ключевое слово C # является псевдонимом для BCLSystem.Int32
, который на самом деле является подтипомobject
(псевдонимSystem.Object
). Фактически,int
базовый класс -System.ValueType
это базовый классSystem.Object
. Попробуйте оценить следующее выражение и смtypeof(int).BaseType.BaseType
. Причина, по которойReferenceEquals
возвращается значение false, заключается в том, чтоint
блок упакован в два отдельных блока, а идентификационные данные каждого блока различны для любого другого блока. Таким образом, операция с двумя боксами всегда дает два объекта, которые никогда не бывают идентичными, независимо от значения в штучной упаковке.System.Int32
илиList<String>.Enumerator
) на самом деле представляет два типа вещей: тип места хранения и тип объекта кучи (иногда называемый «типом в штучной упаковке»). Места хранения, типы которых являются производными,System.ValueType
будут содержать первые; объекты кучи, чьи типы тоже делают, будут содержать последние. В большинстве языков расширяющийся состав существует от первого к последнему, и сужающийся состав от последнего к первому. Обратите внимание, что, хотя типы значений в штучной упаковке имеют тот же дескриптор типа, что и хранилища типов значений, ...Это сводится к деталям реализации: типы значений реализуются иначе, чем ссылочные типы.
Если вы заставите типы значений обрабатываться как ссылочные типы (т. Е. Пометить их, например, путем обращения к ним через интерфейс), вы можете получить дисперсию.
Самый простой способ увидеть разницу - это просто рассмотреть
Array
: массив типов значений объединяется в памяти непрерывно (напрямую), где в качестве массива ссылочных типов имеется только ссылка (указатель) непрерывно в памяти; объекты, на которые указывают, выделяются отдельно.Другая (связанная) проблема (*) заключается в том, что (почти) все ссылочные типы имеют одно и то же представление для целей дисперсии, и большой части кода не нужно знать о разнице между типами, поэтому возможна совместная и противоположная дисперсия (и легко реализовано - часто просто из-за отсутствия дополнительной проверки типов).
(*) Может быть, это та же проблема ...
источник