Почему С# ведет себя по-разному на двух синтаксисах массива int

Массив в С# неявно ссылается на ссылочный тип:

object[] listString = new string[] { "string1", "string2" };

Но не по типу значения, поэтому, если вы измените string на int, вы получите скомпилированную ошибку:

object[] listInt = new int[] {0, 1}; // compile error

Теперь проблема заключается в том, что вы объявляете массив int, как два синтаксиса, ниже которых явно не объявляется тип int, просто дифференцируйте только на new[], компилятор будет обрабатывать по-разному:

object[] list1 = { 0, 1 };       //compile successfully
object[] list2 = new[] {0, 1};    //compile error

Вы получите object[] list1 = { 0, 1 }; скомпилировано успешно, но object[] list2= new[] {0, 1}; скомпилированная ошибка.

Кажется, компилятор С# рассматривает

object[] list1 = { 0, 1 };

а

object[] list1 = new object[]{ 0, 1 };

но

object[] list2 = new[] { 0, 1 };

а

object[] list2 = new int[]{ 0, 1 };  //error because of co-variant

Почему компилятор С# ведет себя по-другому в этом случае?

Ответ 1

В версии, которая компилирует, используется инициализатор массива для инициализации list1. Спецификация языка С#, §1.110 ( "Инициализаторы массива" ):

Инициализатор массива состоит из последовательности инициализаторов переменных, заключенными "{" и "}" жетонами и разделенными "," жетонами. каждый переменный инициализатор является выражением или, в случае многомерный массив, инициализатор вложенного массива.

Контекст в который используется инициализатор массива, определяет тип массива инициализируется. В выражении создания массива тип массива немедленно предшествует инициализатору или выводится из выражения в инициализаторе массива. В поле или переменной объявление, тип массива - это тип поля или переменной, являющейся объявлено.

Когда инициализатор массива используется в поле или переменной объявление, например:

int[] a = {0, 2, 4, 6, 8};

это просто сокращение условного выражения создания массива:

int[] a = new int[] {0, 2, 4, 6, 8};

Итак, очевидно, что это должно скомпилироваться.

Вторая версия использует явное выражение для создания массива, где вы инструктируете компилятор конкретно, какой тип массива следует создавать. §1.51.10.4 ( "Выражения создания массива" ):

Выражение для создания массива третьей формы называется неявно типизированное выражение создания массива. Это похоже на вторая форма, за исключением того, что тип элемента массива не явно заданный, но определяемый как наилучший общий тип (§1.50.2.14) набора выражений в инициализаторе массива.

Следовательно, вторая версия эквивалентна

object[] list2 = new int[] { 0, 1 };

Итак, теперь вопрос теперь становится "почему я не могу назначить int[] для object[]", как вы упомянули в конце вопроса. И ответ также прост, приведенный в §1.109 ( "Ковариация массива" ):

Ковариация массивов конкретно не распространяется на массивы ценностные типы. Например, никакого преобразования не существует, что позволяет int[]для обработки object[].

Ответ 2

Объявление

object[] listInt = new int[] {0, 1};

недействителен, потому что ковариантные преобразования массивов не допускаются для типов значений (а int - тип значения). В качестве альтернативы, декларация

object[] listInt = new string[] {"0", "1"};

является допустимым, поскольку для ссылочных типов допускаются преобразования ковариантных массивов. Это связано с тем, что назначение x = (object)myString включает простое назначение, но y = (object)myInt требует операции бокса.

Теперь о различиях между двумя объявлениями. В объявлении object[] list2 = new[] { 0, 1 } из-за того, как работает вывод типа, он сначала смотрит на выражение правой руки и заключает, что new[] { 0, 1 } следует рассматривать как new int[] { 0, 1 }. Затем он пытается присвоить этот массив int массиву объектов, указав ошибку из-за ковариантного преобразования типов значений. Объявление object[] list1 = { 0, 1 }, однако, использует инициализатор коллекции, и в этих обстоятельствах тип коллекции определяется там, где этот тип определен, поэтому каждый элемент будет заменен на тип, ожидаемый коллекцией.

Ответ 3

Когда вы используете { и }, вы используете инициализаторы коллекции (см. http://msdn.microsoft.com/en-us/library/vstudio/bb384062.aspx). Значения между этими скобками должны быть помещены где-то. Поэтому коллекция должна быть создана. Компилятор проанализирует контекст, чтобы узнать, какая коллекция.

В случае первого: object[] list1 = { 0, 1 }; ясно, что должен быть создан набор. Но что это должно быть? Операции new нет. Существует только один намек: list1 имеет тип object[]. Таким образом, компилятор создает эту коллекцию и заполняет ее знаками.

В вашем втором примере object[] list1 = new[] { 0, 1 }; есть еще один намек: new[]. И этот намек явно говорит: "Будет массив. У этого массива нет типа, поэтому он попытается найти тип массива путем anaylizing значений. Все они int, поэтому он создаст массив int и заполнит его. Другой намек object[] полностью игнорируется, потому что намеки на создание гораздо важнее, чем подсказки, на которые он должен быть назначен. Теперь компилятор хочет назначить этот массив list1 и BOOM: это не подходит!

Ответ 4

Оператор object[] list1 = { 0, 1 }; компилируется, потому что компилятор достаточно умен, чтобы знать, что вы пытаетесь преобразовать массив числовых типов в массив ссылочного типа, поэтому он помещает элементы Int32 в ссылочные типы.

Вы также можете явно указать примитивный тип:

object[] list2 = Array.ConvertAll<int, Object>(new[] { 0, 1 }, input => (Object)input);

Компилятор не будет неявно делать бокс для вас, если вы указали 'int []' или 'Int32 []' в качестве типа массива, но похоже, что это может быть добавлено в С#.

Ответ 5

Инициализатор массива - это удобство компилятора. Если я скажу "Я объявляю массив объектов и присваиваю ему значение", разумно, чтобы компилятор предположил, что ваш { 0, 1 } является массивом объектов и интерпретирует его как таковой. Хотя синтаксис представляется назначением, это не так: вы используете инициализатор. Длительность этого синтаксиса object[] list1 = new object[] { 0, 1 }

Когда вы говорите new[] { 0, 1 }, это выражение, которое создает массив и инициализирует его. Это выражение оценивается независимо от того, для чего вы его назначаете, и поскольку компилятор обнаруживает неявное целочисленное типирование, он создает int[]. Вершина версии этого выражения object[] list2 = new int[] { 0, 1 }

Если вы сравниваете версии с длинными версиями этих двух утверждений, то ясно, где они отличаются.

Ответ 6

object[] listInt = new int[] {0, 1};

является сокращением для

object[] listInt;
listInt = new int[] {0, 1};

который не работает, потому что int[] не ковариантно с object[].

И когда вы говорите new[], это эквивалентно new int[], следовательно, то же самое относится.