Что такое магия массивов в C#

int[] a = new int[5];
string[] b = new string[1];

Типы обоих a а также b наследовать от абстрактного System.Array, но во встроенной библиотеке нет реальных классов (кажется, что есть некоторые типы времени выполнения, вы не можете найти класс определения типа int[]). Можете ли вы сказать мне, что происходит во время компиляции? И почему они (команда C#) сделали этот дизайн (я имею в виду, почему это не что-то вроде Array<T>вместо этого они используют абстрактный класс с магией компилятора)?

8 ответов

Решение

Попытка объяснить это с помощью системы типов.NET не продвинет вас слишком далеко. В компилятор JIT и CLR встроена поддержка ядра для создания массивов. Утверждение как это:

        var arr = new int[5];

Создает этот IL:

  IL_0001:  ldc.i4.5
  IL_0002:  newarr     [mscorlib]System.Int32

Какой JIT-компилятор затем перевести в этот машинный код:

00000035  mov         edx,5                 ; arg2 = array size
0000003a  mov         ecx,6F535F06h         ; arg1 = typeof(int)
0000003f  call        FFD52128              ; call JIT_NewArr1(type, size)

Основными компонентами здесь являются выделенный код операции IL, newarr, вместо обычного кода операции newobj, который создает экземпляр класса. И простой перевод к вспомогательной функции CLR, которая фактически создает объект. Вы можете посмотреть на эту вспомогательную функцию с исходным кодом SSCLI20, clr\src\vm\jithelpers.cpp, Слишком большой для публикации здесь, но он сильно оптимизирован, чтобы сделать этот вид кода максимально быстрым, имея прямой доступ к внутренним объектам типов, доступным для кода CLR.

Доступны два из этих помощников: JIT_NewArr1() создает одномерные (векторные) массивы, а JIT_NewMDArr() создает многомерные массивы. Сравните с двумя перегрузками, доступными для Type.MakeArrayType().

И почему они (команда C#) создали этот дизайн (я имею в виду, почему он не похож на Array...

Обобщения являются идеальными для определения контейнера, поскольку они ограничивают тип элемента, поэтому вы не можете вставить тип A и попытаться получить тип B.

Но дженерики не были добавлены до CLR2/C#2. Поэтому массивы должны были обеспечивать безопасность типов по-своему.

Несмотря на это, это не так уж отличается от дженериков. Вы заметите, что нет специального класса для int[], Но и не будет для Array<int>, В дженериках будет только дженерик Array<T>и CLR "волшебным образом" создает специализированные версии для различных аргументов типа, которые вы используете. Так что было бы не менее "волшебным", если бы использовались дженерики.

Несмотря на это, в CLR тип любого объекта повторно определен (он существует как значение, которым вы можете манипулировать), типа Typeи может быть получен с typeof, Таким образом, хотя нет никакого объявления кода какого-либо типа массива (и зачем вам его нужно видеть?), Существует Type объект, который вы можете запросить.

Кстати, в том, как массивы ограничивают типы элементов, был недостаток дизайна. Вы можете объявить массив:

int[] ints = ...

Затем вы можете сохранить его в более свободной переменной:

object[] objs = ints;

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

objs[3] = "Oh dear";

Во время выполнения он выдает исключение. Идея статической проверки типов заключается в том, чтобы отлавливать подобные вещи во время компиляции, а не во время выполнения. Обобщения не имели бы этой проблемы, потому что они не обеспечивают совместимость присваивания экземплярам обобщенного класса на основе совместимости их параметров типа. (Начиная с C#4/CLR4 они получили возможность делать это там, где это имеет смысл, но это не имеет смысла для изменяемого массива.)

Посмотрите на Array учебный класс.

При объявлении массива с помощью [] синтаксис, компилятор, за кулисами будет использовать этот класс для вас.

Для C# [] становится типом, который наследуется от System.Array,

Из спецификации C# 4.0:

§12.1.1 Тип System.Array

Тип System.Array является абстрактным базовым типом всех типов массивов. Неявное ссылочное преобразование (§6.1.6) существует из любого типа массива в System.Array, а явное ссылочное преобразование (§6.2.4) существует из System.Array в любой тип массива. Обратите внимание, что System.Array сам по себе не является типом массива. Скорее, это тип класса, из которого получены все типы массивов.

Я бы порекомендовал получить спецификацию ECMA 335 и поиск массивов, если вы хотите узнать подробности низкого уровня: http://www.ecma-international.org/publications/standards/Ecma-335.htm

Есть такой класс. Вы не можете наследовать его, но когда вы пишете "int[]", компилятор создает тип, который наследует System.Array. Итак, если вы объявите переменную:

int[] x;

Эта переменная будет иметь тип, который наследует System.Array, и, следовательно, имеет все свои методы и свойства.

Это также похоже на делегатов. Когда вы определяете делегата:

delegate void Foo(int x);
delegate int Bar(double x);

Тогда тип Foo на самом деле класс, который наследует System.MulticastDelegate а также Bar это класс, который наследует System.Delegate,

Я начал копаться в спецификации ECMA 335 и решил, что поделюсь тем, что я прочитал.

Точные типы массивов создаются автоматически VES, когда они необходимы. Следовательно, операции над типом массива определяются CTS. Обычно это: выделение массива на основе размера и информации о нижних границах, индексация массива для чтения и записи значения, вычисление адреса элемента массива (управляемого указателя) и запрос на ранг, границы и общее количество значений, хранящихся в массиве.

VES создает один тип массива для каждого различимого типа массива.

Векторы являются подтипами System.Array, абстрактного класса, предварительно определенного CLI. Он предоставляет несколько методов, которые можно применять ко всем векторам. Смотрите Раздел IV.

В то время как векторы (§II.14.1) имеют прямую поддержку через инструкции CIL, все другие массивы поддерживаются VES путем создания подтипов абстрактного класса System.Array (см. Раздел IV)

В то время как векторы (§II.14.1) имеют прямую поддержку через инструкции CIL, все другие массивы поддерживаются VES путем создания подтипов абстрактного класса System.Array (см. Раздел IV)

Класс, который VES создает для массивов, содержит несколько методов, реализация которых обеспечивается VES:

Далее довольно подробно говорится, что предоставленные методы:

  1. Два конструктора
  2. Получить
  3. Задавать
  4. Адрес (возвращает управляемый указатель)

VES означает Virtual Execution System, а CLR является ее реализацией.

Спецификация также подробно описывает, как хранить данные массива (непрерывно в порядке основной строки), какая индексация разрешена в массивах (только на основе 0), когда создается вектор (одномерные массивы на основе 0), в отличие от в другой тип массива, когда инструкция CIL newarr используется в отличие от newobj (создание 0-мерного одномерного массива).

В основном все, что компилятор должен делать для построения таблиц поиска методов и т. Д. Для обычного типа, это относится и к массивам, но они просто запрограммировали более универсальное и немного специальное поведение в компиляторе / JIT.

Почему они это сделали? Возможно, потому что массивы являются специальными, широко используются и могут храниться оптимизированным способом. Однако команда C# не обязательно приняла это решение. Это больше похоже на.NET, двоюродный брат Mono и Portable.NET, которые все являются CIL.

Массивы являются специальными для CLR. Они назначаются инструкцией 'newarr', а доступ к элементам осуществляется с помощью инструкций 'ldelem*' и 'stelem*', а не с помощью методов System.Array;

см. http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.newarr.aspx

Вы можете проверить вывод ildasm, чтобы увидеть, как представлены массивы.

Итак, чтобы ответить на ваш вопрос - не создается новое объявление типа для какого-либо конкретного массива.

[] - это синтаксис (синтаксический сахар) для определения массивов в C#. Возможно CreateInstance будет заменен во время выполнения

 Array a = Array.CreateInstance(typeof(int), 5); 

такой же как

int[] a = new int[5];

Источник для CreateInstance (взят из отражателя)

public static unsafe Array CreateInstance(Type elementType, int length)
{
    if (elementType == null)
    {
        throw new ArgumentNullException("elementType");
    }
    RuntimeType underlyingSystemType = elementType.UnderlyingSystemType as RuntimeType;
    if (underlyingSystemType == null)
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeType"), "elementType");
    }
    if (length < 0)
    {
        throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }
    return InternalCreate((void*) underlyingSystemType.TypeHandle.Value, 1, &length, null);
}
Другие вопросы по тегам