Массивы с ненулевой базой и интерфейс

ОГЛАВЛЕНИЕ

Статья, объясняющая новичку размерности массивов и интерфейсы.

Введение

Массивы представляют собой фиксированное количество элементов конкретного типа, тем самым позволяя рассматривать несколько элементов как единый набор. Массивы в .NET в основном идут с отсчетом от нуля, и все типы массивов неявно унаследованы от абстрактного класса System.Array abstract, который сам унаследован от корневого класса System.Object. Отсюда следует, что массивы всегда являются ссылочными типами, размещаемыми в управляемой куче (в отличие от расположения в ряд в стеке потока в виде последовательности байтов) и что переменное или поле приложения содержит ссылку на массив, а не элементы самого массива. Эта статья делает упор на использование массивов с ненулевой нижней границей. Перед этой темой надо коснуться некоторых основ System.Array. Чтобы объявить и отсортировать массив:

using System;
class Test {
 static void Main() {
 int[]  ar = { 3, 1, 2 };       // объявить и инициализировать массив
Array.Sort(ar);                   // вызвать общий/статический метод
Console.WriteLine("{0},  {1}, {2}",  ar[0], ar[1], ar[2]);  //отобразить результат
   }
}

Вывод: 3, 1, 2

Читателю заметна простота этого кода, и многие недоумевают, как он может использоваться практически. Это показано позже. В C# ( и CLR - общеязыковая среда исполнения) массив обозначается квадратными скобками после типа элемента. Например: char[] vowels = new char[5]; Квадратные скобки также индексируют массив, обращаясь к конкретному элементу:

char[] vowels = new char [5];
 vowels [0] = 'A';
 vowels [1] = 'E';
 vowels [2] = 'I';
 vowels [3] = 'O';
 vowels [4] = 'U';
Console.WriteLine([i]);   // где результат - “E”.

Цикл for – одна управляющая конструкция, проходящая по всем элементам в массиве, следовательно, она обойдет в цикле целое число I от 0 до 4:

using System;
class Program {
static void Main() {
 char[] vowels = new char [5];
 vowels [0] = 'A';
 vowels [1] = 'E';
 vowels [2] = 'I';
 vowels [3] = 'O';
 vowels [4] = 'U';

for (int i = 0; i < vowels.Length; i++)
 {
   Console.Write( vowels [i]);
        }
    }
}

Вывод: A, E, I, O, U

Свойство length(длина) массива возвращает количество элементов в массиве. Эти массивы печатают вывод строки буквенно-цифровых символов в виде массива.
CLR позволяет создавать и работать с массивами, имеющими ненулевые нижние границы. Чтобы создать динамический массив, можно вызвать статический метод CreateInstance() класса System.Array. Существует несколько перегрузок этого метода, позволяющих задать тип элементов в массиве, количество размерностей в массиве, нижние границы каждой размерности и количество элементов в каждой размерности. Математически, точка на отрезке прямой одномерна. Отрезок прямой двумерен, и геометрический круг или квадрат трехмерен. CreateInstance выделяет память для массива, сохраняет информацию о параметре в части служебных данных блока памяти массива и возвращает ссылку на массив. Код ниже показывает, как динамически создать 2-мерный массив значений System.Decimal. Первая размерность представляет календарные годы с 2008 по 2012. Несмотря на то, что при написании статьи 2010 еще не наступил, использование массива показывает, растет ли доход (в идеале при развитии компании: гипотетический сценарий). Вторая размерность представляет финансовые кварталы с 1 по 4 включительно.

Массивы с ненулевой нижней границей

using System;
public sealed class Program {
 public static void Main() {
 Int32[] lowerBounds = { 2008, 1 };
 Int32[] lengths     = {   5,   4};
 Decimal[,] quarterlyIncome = (Decimal[,])
  Array.CreateInstance(typeof(Decimal), lengths, lowerBounds);
  Console.WriteLine("{0,4}  {1, 9}  {2, 9} {3, 9}, {4, 9}",
                "Year", "Q1", "Q2", "Q3" , "Q4");
  Int32 firstYear =  quarterlyIncome.GetLowerBound(0);
  Int32 lastYear =   quarterlyIncome.GetUpperBound(0);
  Int32 firstQuarter =  quarterlyIncome.GetLowerBound(1);
   Int32 lastQuarter = quarterlyIncome.GetUpperBound(1);

   for (Int32 year = firstYear; year <= lastYear; year++) {
  Console.Write(year + " ");
   for (Int32 quarter = firstQuarter; quarter <= lastQuarter; quarter++) {
  Console.Write("{0,9:c}  ",  quarterlyIncome[year, quarter]);
   }
  Console.WriteLine();
   }
       }
 }

Производительность доступа к массиву

Как сказано выше, массивы – способ рассматривать несколько элементов как единую коллекцию. Коллекции начинаются там, где массивы кончаются, и стоит надеяться, что новичок сможет дойти до коллекций и обобщений. Но вспомните, что ссылка на сам массив хранится в управляемой куче, а не элементы. Стало быть, доступ к элементам из массива является показателем производительности. Внутри CLR поддерживает два разных вида массивов:
•    Одномерные массивы с нижней границей 0
•    Одномерные и многомерные массивы с неизвестной нижней границей

Код ниже сначала покажет, что когда массив имеет два и более размерностей, можно привести ссылку, возвращенную из CreateInstance(), к переменной ElementType[] (где ElementType – имя некоторого типа), что облегчает доступ к элементам в массиве. Если массив имеет одну размерность, то в C# надо использовать методы GetValue и SetValue класса Array для доступа к элементам массива.

     using System;
     public static class Program {
     public static void Main() {
    Array a;
         a = new String[0];
    Console.WriteLine(a.GetType());    // System.String[]
         a = Array.CreateInstance(typeof(String),
             new Int32[] { 0 }, new Int32[] { 0 });
    Console.WriteLine(a.GetType());    // System.String[]
         a = Array.CreateInstance(typeof(String),
             new Int32[] { 0 }, new Int32[] { 1 });
    Console.WriteLine(a.GetType());    // System.String[*] 
      Console.WriteLine();
                            
     a = new String[0, 0];
     Console.WriteLine(a.GetType());    // System.String[,]
     a = Array.CreateInstance(typeof(String), new Int32[] { 0, 0 }, new Int32[]
                 { 0, 0 });
        Console.WriteLine(a.GetType());    // System.String[,]
                              a = Array.CreateInstance(typeof(String),
                               new Int32[] { 0, 0 },
                               new Int32[] { 1, 1 });
        Console.WriteLine(a.GetType());    // System.String[,]
    }
}

Обратите внимание на комментарии, идущие за каждым Console.WriteLine, означающим вывод. Для одномерного массива с отсчетом от нуля отображается имя типа System.String[]. Массив с отсчетом от единицы отображает тип System.String[*]. * сообщает CLR, что это не массив с отсчетом от нуля. Для многомерных массивов Console.WriteLine отображает тип System.String[,]. Ясно, что доступ к элементам одномерного массива с отсчетом от нуля быстрее, чем доступ к элементам массивов с отсчетом не от нуля. Причина этого находится в метаданных и в коде промежуточного языка, генерируемых компилятором CSC.exe. Введя с клавиатуры команду ildasm /metadata=validate /out: Access.il Access.il для типа ‘type Access.il’, можно изучить и проанализировать код промежуточного языка. Возникает мысль, что компилятор C# может вводить в заблуждение. При компиляции создаются лишние объекты, и код должен оптимизироваться с помощью переключателя /optimize+. Предпочтительный инструмент анализа кода - ildasm.exe. Ниже дан фрагмент кода промежуточного языка (без таблиц метаданных):

{
    .entrypoint
    // Code size       62 (0x3e)
    .maxstack  5
    .locals init (int32[] V_0)
    IL_0000:  nop
    IL_0001:  ldc.i4.3
    IL_0002:  newarr     [mscorlib]System.Int32 //NOTE
    IL_0007:  dup
    IL_0008:  ldtoken    field valuetype '<privateimplementationdetails>{AD4A1C2
5-8CEB-439C-88EC-E7E4AD0D78E3}'/'__StaticArrayInitTypeSize=12'
 '<privateimplemen>{AD4A1C25-8CEB-439C-88EC-E7E4AD0D78E3}'::'$$method0x6000001-1'
    IL_000d:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeH
elpers::InitializeArray(class [mscorlib]System.Array,

                        valuetype [mscorlib]System.RuntimeFieldHandle)
    IL_0012:  stloc.0
    IL_0013:  ldloc.0
    IL_0014:  call       void [mscorlib]System.Array::Sort<int32>(!!0[])
    IL_0019:  nop
    IL_001a:  ldstr      "{0},  {1}, {2}"
    IL_001f:  ldloc.0
    IL_0020:  ldc.i4.0
    IL_0021:  ldelem.i4   //NOTE
    IL_0022:  box        [mscorlib]System.Int32
    IL_0027:  ldloc.0
    IL_0028:  ldc.i4.1
    IL_0029:  ldelem.i4  //NOTE
    IL_002a:  box        [mscorlib]System.Int32
    IL_002f:  ldloc.0
    IL_0030:  ldc.i4.2
    IL_0031:  ldelem.i4
    IL_0032:  box        [mscorlib]System.Int32
    IL_0037:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object,
                                                                  object,
                                                                  object)
    IL_003c:  nop
    IL_003d:  ret
  } // end of method Test::Main

Есть специальные команды промежуточного языка, такие как newarr, ldelem, ldelema, ldlen, и stelen, используемые для обработки одномерных массивов с отсчетом от нуля. Эти специальные команды промежуточного языка заставляют динамичный компилятор генерировать оптимизированный код.