Смутно о боксе, кастинге, неявном и т.д.
Из книги:
1) int i = 7;
2) Object o = i; // Implicit boxing int-->Object
3) Object[] a3 = new int[] { 1, 2 }; // Illegal: no array conversion
Назначения в 3) являются незаконными потому что int не является ссылочным типом и поэтому int [] неявно конвертируется в Object []
Я не понимаю. в строке 2) он показывает, что int неявно конвертируется в Object, а в третьей строке он говорит, что int [] неявно конвертируется. wha??
Ответы
Ответ 1
Не повторять проблему, но это потому, что int[]
неявно (или явно, если на то пошло) конвертируется в Object[]
.
Массивы сами по себе являются типами. int[]
просто означает "ряд переменных int
", так же как Object[]
означает "ряд переменных Object
".
Кстати, все массивы являются ссылочными типами, просто int[]
является ссылочным типом, представляющим серию определенного типа значения.
ИЗМЕНИТЬ
Не позволяйте разговорам о ковариации путать вас. Это очень непонятная тема, и это не очень полезно для начинающего разработчика, чтобы попытаться решить. Да,.NET 4.0 вводит возможность указывать операцию как ковариантную или контравариантную, но это не позволит совместимости присваивания между несовместимыми типами, такими как Object[]
и int[]
. Рассмотрим следующий пример, предполагая, что он будет компилироваться.
int[] ints = new int[] { 1, 2, 3 };
Object[] objects = ints;
objects[1] = "hello";
int foo = ints[1];
Теперь у нас есть проблема. Если было законным присваивать int[]
значению Object[]
, то теперь я неожиданно (магически) имел значение string
в моем массиве int[]
- то, чего никогда не должно было случиться, и на самом деле этого не может произойти, поскольку переменная int
не может содержать значение string
.
Другие (правильно) указали, что что-то вроде этого компилируется:
string[] strings = new string[] { "a", "b", "c" };
Object[] objects = strings;
objects[1] = 4;
Я оставлю его кому-то вроде Эрика Липперта, чтобы объяснить, почему этот вид работы работает (он по существу предполагает ковариацию, когда это не обязательно так) [ EDIT: Благодаря Тиму Гудману, который фактически публикует объяснение Эрика или, по крайней мере, выражение об этом], но принципиально любой ссылочный тип технически способен содержать ссылку на любой тип. Другими словами, когда я объявляю переменную string
, она выделяет тот же объем памяти (для переменной), как если бы я объявлял переменную DbConnection
; они оба являются ссылочными типами. Для типов значений объем выделенной памяти зависит от типа, и они принципиально несовместимы.
Однако вы заметите, что при выполнении последнего шага вы получите исключение во время выполнения (ArrayTypeMismatchException
) (присваивание int
второму элементу массива), так как базовый массив на самом деле является string[]
.
Ответ 2
Ясно, что это путаная проблема. К сожалению, ответ Тима Гудмана, который, по моему мнению, является тем, который наиболее четко и правильно рассмотрел фактический заявленный вопрос, был опущен и удален. Ответ Ли также очень хорошо поражает.
Чтобы суммировать все это, позвольте мне перефразировать вопрос на несколько более точных вопросов.
Что такое "ковариация", как это применимо к совместимости с ассистентом?
Вкратце: рассмотрим сопоставление от одного типа к другому. Скажем, int --> int[]
, string --> string[]
и так далее. То есть отображение "x отображает в массив x". Если это отображение сохраняет компиляцию присваивания, то оно является ковариантным отображением. Короче говоря, мы говорим, что "массивы являются ковариантными", что означает, что правила совместимости присвоений массива такие же, как правила совместимости присвоений их типов элементов, потому что сопоставление от типа элемента к типу массива является ковариантным ".
Подробнее о взаимосвязи между ковариацией и совместимостью присваивания см.:
Разница между ковариацией и контрацепцией
http://blogs.msdn.com/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx
Являются ли массивы фактически ковариантными в С#?
Они ковариантны только тогда, когда тип элемента является ссылочным типом.
Является ли такой тип ковариации безопасным?
Нет. Это сломано. Отсутствие безопасности типа означает, что операции, которые преуспевают во время компиляции, могут вызывать исключения во время выполнения. Это также означает, что каждое присваивание массиву, которое может вызвать такое исключение, должно проверять, нужно ли бросать исключение, что дорого. В основном то, что мы имеем здесь, является опасной, дорогостоящей функцией, от которой вы не можете отказаться. Я не очень-то доволен этим, но это то, с чем мы застряли.
Подробнее об этой сломанной форме ковариации см. http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx.
Поддерживает ли CLR ковариацию на типизированных массивах значений?
Да. См.
http://blogs.msdn.com/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent.aspx
Что я написал из своего ответа на:
Почему мой массив С# теряет информацию о знаке типа при передаче на объект?
Поддерживает ли С# безопасные формы ковариации?
Как и на С# 4, да. Мы поддерживаем ковариацию и контравариантность общих интерфейсов и делегатов, которые параметризуются ссылочными типами. С# 3 не поддерживает общую дисперсию.
Например, в С# 4 объект, реализующий IEnumerable<string>
, может быть назначен переменной типа IEnumerable<object>
.
См. http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx
для расширенного обсуждения этой функции.
Теперь, когда у нас есть предварительные условия, мы можем подойти к вашему актуальному вопросу:
Почему общая и ковариация массива работают только по ссылочным типам, а не по типам значений?
Поскольку некоторые преобразования сохраняются в виде, а некоторые из них меняются.
Когда вы говорите
string x = "hello";
object y = x;
содержимое хранилища y точно совпадает с содержимым хранилища x. Оба они представляют собой бит-шаблон, который означает "ссылаться на этот объект на куче GC". Этот битовый шаблон тот же, независимо от того, интерпретирует ли CLR биты как ссылку на объект или ссылку на строку.
Массив - это не что иное, как целая куча хранилищ. Когда вы переосмысливаете содержимое строки [] как ссылки на объекты, ничто в их битах не должно меняться. Они просто остаются точно такими же, и CLR думает о них как о объектах, а не о строках. Вы просто смотрите на строку смешно и ее объект.
Когда вы конвертируете int в объект, мы выделяем поле для ввода int. Int представляет собой 32-битное целое число, содержащее его собственное значение. Коробка представляется как 32 или 64-битный управляемый адрес в кучу GC, которая затем содержит 32-битное значение int. Это совершенно разные бит! Превращение объекта в int требует огромного количества работы. Вы не можете просто взглянуть на int funny и эй, это похоже на объект. Вы должны выделить память для выполнения этой работы.
И поэтому вы не можете превратить массив int в массив объектов. Массив из десяти целых чисел может принимать 320 бит, причем каждый бит является частью самого целого. Массив из десяти объектов - это 320 или 640 бит управляемых адресов в кучу GC, и кто будет делать все это распределение? Мы хотим, чтобы конверсии ссылок были быстрыми и дешевыми; это преобразование потребует от нас в основном выделить целый новый массив и сделать копии всего содержимого; результат больше не будет иметь ссылочного идентификатора с исходным массивом, поэтому изменения в нем будут потеряны. Или нам пришлось бы написать еще больше кода, который бы вносил изменения в новый массив и переводил их обратно в старый.
См.
http://ericlippert.com/2009/03/03/representation-and-identity/
для большего обсуждения преобразований, сохраняющих представление.
Отвечает ли это на ваш вопрос? Я знаю, это запутанная тема. Он довольно глубоко вписывается в основные проектные решения CLR.
Ответ 3
Это потому, что int - тип значения. Такое преобразование было бы законным для ссылочных типов.
Согласно Эрик Липперт команды компилятора С#:
Правило ковариации массива С# является "если X является ссылочным типом, неявно преобразованным в ссылочный тип Y, тогда X [] неявно конвертируется в Y []"
Вопрос спрашивает, почему это противоречит тому факту, что int преобразуется в объект. Обратите внимание, что int наследует объект, но int [] не наследует объект []. Скорее int [] и object [] оба наследуются от System.Array
ИЗМЕНИТЬ
Я нашел больше от Eric о том, почему такой тип преобразования разрешен для ссылочных типов.
Он был добавлен в CLR, потому что Java требует этого, и разработчики CLR хотели иметь возможность поддерживать Java-подобные языки. Затем мы добавили его в С#, потому что он был в CLR. В то время это решение было довольно противоречивым, и я не очень этому доволен, но теперь мы ничего не можем с этим сделать.
Обратите внимание, однако, что правило С# для ковариации массива фактически тонко отличается от правила CLR, как описано здесь здесь. Короче говоря, правило CLR является "если X является назначением, совместимым с Y, тогда X [] является присвоением, совместимым с Y []".
ИЗМЕНИТЬ
См. также мой другой ответ, который я удалил, прежде чем отправлять его, но с тех пор он восстановлен.
Ответ 4
EDIT: Undeleting this (несмотря на пониженные голоса) из-за положительного комментария Эрика Липперта.
В многочисленных ответах упоминается, что int [] не конвертируется в объект []., это, безусловно, верно, но на самом деле не возникает вопрос о том, почему это так. Не смысл вкладывать слова в уставы языковых дизайнеров, но я подозреваю, что это связано с тем, что процесс преобразования int [] в объект [] требует выделения целого нового массива и бокса каждого элемента массива int в элемент массив объектов... это дорогостоящая операция, которая, вероятно, не та, что намеревается программист. Скорее всего, вы хотите поместить весь int [] в один объект.
Ответ 5
В С# разрешено следующее:
object[] objects = new string[] { "a", "b", "c" };
Итак, вы можете ожидать, что
object[] ints = new int[] { 1, 2, 3}
также действителен, так как подобная строка, int неявно конвертируется в объект. Однако в первом случае string[]
содержит последовательность ссылок на строки, которые также являются действительными ссылками на объекты. Во втором случае int[]
содержит последовательность значений int, и тезисы не являются действительными ссылками на объекты. Теперь вы можете назначить int для объекта, как показывает ваш пример, но это вызывает создание нового объекта (бокса). Следовательно, для этого справедливо:
object[] ints = new int[] { 1, 2, 3}
потребовалось бы создание нового массива, тогда как первое - только одна копия ссылки. Поэтому решение состоит в том, чтобы явно создать новый массив:
object[] ints = (new int[] { 1, 2, 3}).Select(i => (object)i).ToArray();