Массивы с ненулевой базой и интерфейс
ОГЛАВЛЕНИЕ
Статья, объясняющая новичку размерности массивов и интерфейсы.
Введение
Массивы представляют собой фиксированное количество элементов конкретного типа, тем самым позволяя рассматривать несколько элементов как единый набор. Массивы в .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, используемые для обработки одномерных массивов с отсчетом от нуля. Эти специальные команды промежуточного языка заставляют динамичный компилятор генерировать оптимизированный код.