Массивы с ненулевой базой и интерфейс
ОГЛАВЛЕНИЕ
Статья, объясняющая новичку размерности массивов и интерфейсы.
Введение
Массивы представляют собой фиксированное количество элементов конкретного типа, тем самым позволяя рассматривать несколько элементов как единый набор. Массивы в .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, используемые для обработки одномерных массивов с отсчетом от нуля. Эти специальные команды промежуточного языка заставляют динамичный компилятор генерировать оптимизированный код.
Интерфейсы
Любой, кто когда-либо занимался программированием COM(модель составных объектов), должен знать интерфейсы. В COM на интерфейс указывают, когда есть попытка обратиться к компоненту COM. Интерфейс является сопряжением между группами семантически связанных функций, на которые указывают виртуальные указатели в виртуальной таблице. Но в C++ наследование класса не всегда то же самое, что и наследование интерфейса. В каркасе Microsoft .NET есть класс System.Object, определяющий 4 открытых метода экземпляра: ToString(), Equals(), GetHashCode() и GetType(). Рассмотрите знакомый код “Привет, мир!”.
using System
class App {
static void Main() {
Console.WriteLine(“Привет, мир!”);
}
}
Привет, мир!
Теперь запускается программа, загружающая этот управляемый модУль и использующая API(интерфейс прикладного программирования) Reflection для генерации того, что рассмотрено выше:
using System;
using System.IO;
using System.Reflection;
public class Meta {
public static int Main() {
Assembly a = Assembly.LoadFrom("hello.exe");
Module[] m = a.GetModules();
Type[] types = m[0].GetTypes();
Type type = types[0];
Console.WriteLine("Тип [{0}] имеет следующие методы:", type.Name);
MethodInfo[] mInfo = type.GetMethods();
foreach (MethodInfo mi in mInfo)
{
Console.WriteLine(" {0}", mi);
}
return 0;
}
}
Этот код компилируется вместе с загрузкой сборки в метод LoadFrom класса Assembly:
C:\Windows\Microsoft.NET\Framework\v2.0.50727>csc /optimize+ meta.cs
C:\Windows\Microsoft.NET\Framework\v2.0.50727>meta
Тип [App] имеет следующие методы:
System.String ToString()
Boolean Equals(System.Object)
Int32 GetHashCode()
System.Type GetType()
Поскольку пространство имен System.Object является корневым классом, все прочие классы наследуют те четыре открытых метода экземпляра. Любой класс, производный от Object, наследует следующее:
• Сигнатуры методов
• Реализацию этих методов
Интерфейс – другой способ определения именованного набора сигнатур методов. Интерфейс может наследовать другие интерфейсы. Например, интерфейс ICollection<t> обязан реализовывать все методы, определенные интерфейсы ICollection<t>,IEnumerable<t>, и IEnumberable.
Определение типа, реализующего интерфейс
Интерфейс System.IComparable<t> определен в Mscorlib.dll так:
public interface IComparable<t> {
Int32 CompareTo( T other);
}
Приведенный ниже код показывает, как определить тип, реализующий этот интерфейс, а также показывает код, сравнивающий два объекта Point(точка):
using System;
// Point унаследован от System.Object и реализует IComparable<t>.
public sealed class Point : IComparable<point> {
private Int32 m_x, m_y;
public Point(Int32 x, Int32 y) {
m_x = x;
m_y = y;
}
// Этот метод реализует IComparable<t> для Point
public Int32 CompareTo(Point other) {
return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)
- Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y));
}
public override String ToString() {
return String.Format("({0}, {1})", m_x, m_y);
}
}
public static class Program {
public static void Main() {
Point[] points = new Point[] {
new Point(3, 3),
new Point(1, 2),
};
if (points[0].CompareTo(points[1]) > 0) {
Point tempPoint = points[0];
points[0] = points[1];
points[1] = tempPoint;
}
Console.WriteLine("Точки от самой близкой к (0, 0) до самой дальней:");
foreach (Point p in points)
Console.WriteLine(p);
}
}
Вывод
Точки от самой близкой к (0, 0) до самой дальней:
(1, 2)
(3, 3)