Использование ссылочных типов
В дополнение к возможности объявлять массивы предопределённых типов, можно также объявлять массивы специальных пользовательских типов. Начнем с класса Person,у которого есть автоматически реализуемые свойства FirstNameи LastNameи переопределенный метод ToString() из класса Object:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
Объявление массива из двух элементов Person подобно объявлению массива целых чисел:
Person[] myPersons = new Person[2];
Однако следует помнить, что если элементы массива относятся к ссылочному типу, то для каждого из нйх должна быть выделена память. При обращении к элементу массива, для которого память не распределялась, генерируется исключение NullReferenceException.
Всю необходимую информацию об исключениях и ошибках вы найдете в разделе 6.15.
Для выделения памяти под каждый элемент можно использовать индексатор, начинающийся с 0:
new Person { FirstName="Ayrton", LastName="Senna" };
new Person { FirstName="Michael", LastName="Schumacher" };
На рис. 6.2 показаны объекты в управляемой куче, относящиеся к массиву Person. Переменная myPersons сохраняется в стеке. Эта переменная ссылается на массив элементов Person,хранящихся в управляемой куче. Данный массив имеет достаточно места для двух ссылок. Каждый элемент массива ссылается на объект Person,также находящийся в управляемой куче.
Рисунок 6.2 - Объекты в управляемой куче, относящиеся к массиву Person
Как и в случае с типом int, инициализатор массива можно также применять с пользовательским типом:
Person[] myPersons2 =
{
new Person { FirstName="Ayrton", LastName="Senna"},
new Person { FirstName="Michael", LastName=”Schumacher"}
};
Многомерные массивы
Обычные массивы (также называемые одномерными) индексируются единственным целым числом. Многомерный массив индексируется двумя и более целыми числами. На рис. 6.3 показано математическое обозначение двумерного массива, имеющего три строки и три столбца. Первая строка содержит значения 1, 2 и 3, а третья -7, 8 и 9.
Рисунок 6.3 - Математическое обозначение двумерного массива
Чтобы объявить двумерный массив на С#, необходимо поместить запятую внутрь квадратных скобок. Массив инициализируется указанием размера каждого измерения (также называется рангом). Затем к элементам массива можно обращаться, указывая два целых числа в индексаторе:
int[,] twodim = new int[3,3] ;
twodim[0, 0] = 1;
twodim[0, 1] = 2;
twodim[0, 2] = 3;
twodim[1, 0] = 4;
twodim[l, 1] = 5;
twodim[1, 2] = 6;
twodim[2, 0] = 7;
twodim[2, 1].= 8;
twodim[2, 2] = 9;
После объявления массива изменить его ранг невозможно.
Если заранее известно количество элементов, то двумерный массив также можно инициализировать с использованием индексатора массива. Для инициализации массива применяется одна внешняя пара фигурных скобок, и каждая строка инициализируется с использованием фигурных скобок, расположенных внутри этой внешней пары скобок.
int [,] twodim = {
{1, 2, 3),
{4, 5, 6},
{7, 8, 9}
};
При использовании инициализатора массива должен инициализироваться каждый его элемент. Пропускать инициализацию каких-то значений не допускаётся.
int[,,] threedim = {
{ { 1, 2 }, { 3, 4 } },
{ { 5, 6 }, { 7, 8 } },
{ { 9, 10 }, { 11, 12 } }
};
Console.WriteLine(threedim[0, 1, 1]);
Зубчатые массивы
Двумерный массив имеет прямоугольную форму (например, размером 3x3 элемента), Зубчатый (jagged) массив более гибок в отношении размерности. В таких массивах каждок строка может иметь отличающийся размер.
На рис. 6.4 демонстрируется отличие обычного двумерного массива от зубчатого. Показанный здесь зубчатый массив содержит три строки, причем первая строка имеет два элемента, вторая - шесть элементов, а третья - три элемента.
Рис. 6.4. - Различие между обычным двумерным и зубчатым массивом
Зубчатый массив объявляется размещением пар открывающих и закрывающих квадратных скобок друг за другом. При инициализации зубчатого массива в первой паре квадратных скобок указывается только размер, определяющий количество строк. Вторая пара квадратных скобок, определяющая количество элементов внутри строки, остается пустой, поскольку каждая строка может содержать отличающееся количество элементов. Затем для каждой строки может быть установлено количество элементов в ней:
int[] [ ] jagged = new int[3] [ ] ;
jagged[0] = new int [2] { 1, 2 };
jagged[l] = new int[6] { 3, 4, 5, 6, 7, 8 };
jagged[2] = new int[3] { 9, 10, 11 };
Итерация по всем элементам зубчатого массива может осуществляться с помощью вложенных циклов for. Во внешнем цикле for выполняется проход по всем строкам, а,во внутреннем for - проход по каждому элементу строки:
for (int row = 0; row < jagged.Length; row++)
{
for (int element = 0; element < jagged[row].Length; element++)
{
Console.WriteLine("строка: {0}, элемент: {1}, значение: {2}",
row, element, jagged[row][element]);
}
}
Вывод этой итерации отображает строки и все элементы в строках:
строка: 0, элемент: 0, значение: 1
строка: 0, элемент: 0, значение: 2
строка: 1, элемент: 0, значение: 3
строка: 1, элемент: 1, значение: 4
строка: 1, элемент: 2, значение: 6
строка: 1, элемент: 3, значение: 1
строка: 1, элемент: 4, значение: 7
строка: 1, элемент: 5, значение: 8
строка: 2, элемент: 0, значение: 9
строка: 2, элемент: 1, значение: 10
строка: 2, элемент: 2, значение: 11
Класс Array
Объявление массива с квадратными скобками - это нотация C# для использования класса Array.Такой синтаксис C#приводит к созданию “за кулисами” нового класса, унаследованного от абстрактного базового класса Array.Таким образом, методы и свойства, определенные в классе Array,можно использовать с любым массивом С#.Например, вы уже применяли свойство Lengthи итерацию по элементам с помощью оператора foreach.В этом случае используется метод GetEnumerator() класса Array.
В классе Arrayреализованы и другие свойства: LongLengthдля массивов, в которых количество элементов не помещается в обычное, целое, и Rankдля получения количества измерений. Давайте взглянем на другие члены класса Array,попробовав поддерживаемые им возможности.
Создание массивов
Класс Arrayявляется абстрактным, поэтому создать массив с использованием какого-либо конструктора нельзя. Однако вместо применения синтаксиса C# для создания экземпляров массивов также возможно создавать их с помощью статического метода Createlnstance(). Это исключительно удобно, когда заранее не известен тип элементов массива, поскольку тип можно передать методу Createlnstance() в параметре как объект Туре.
В следующем примере демонстрируется создание массива типа intразмером 5. Первый аргумент метода Createlnstance() требует тип элементов, а второй определяет размер. Для установки значений служит метод SetValue(), а для их чтения - метод GetValue().
Array intArray1 = Array.Createlnstance (typeof (int), 5) ;
for (int i = 0;- i < 5; i++)
{
intArray1.SetValue(33, i);
}
for (int i = 0; i < 5; i++)
{
Console.WriteLine(intArray1.GetValue(i));
}
Созданный массив можно также привести к типу массива, объявленного как int[]:
int[] intArray2 = (int[])intArray1;
Метод Createlnstance() имеет множество перегрузок для создания многомерных массивов, а также для создания массивов с индексацией, не начинающейся с 0. В следующем примере создается двумерный массив размером 2x3 элемента. Базой первого измерения является 1, а второго - 10.
int[] lengths = {2, 3};
int[] lowerBounds = {1, 10};
Array racers = Array.Createlnstance(typeof(Person), lengths,
lowerBounds);
Метод SetValue() , устанавливающий элементы массива, принимает индексы каждого измерения:
racers.SetValue (new Person
{
FirstName = "Alain",
LastName = "Prost"
}, 1, 10);
racers.SetValue(new Person
{
FirstName = "Emerson",
LastName = "Fittipaldi"
}, 1, 11);
racers.SetValue(new Person {
FirstName = "Ayrton",
LastName = "Senna"
}, 1, 12);
racers.SetValue(new Person
{
FirstName = "Ralf",
LastName = "Schumacher"
), 2, 10);
racers.SetValue(new Person
{
FirstName = "Fernando",
LastName = "Alonso"
}, 2, 11);
racers.SetValue(new Person
{
FirstName = "Jenson",
LastName = "Button"
}, 2, 12);
Хотя массив не базируется на 0, его можно присваивать переменной в обычной нотации С#. Следует лишь обращать внимание на то, чтобы не выходить за границы индексов:
Person[,] racers2 = (Person[,])racers;
Person first = racers2[l, 10];
Person last = racers2[2, 12];
Копирование массивов
Поскольку массивы - это ссылочные типы, присваивание переменной типа массива другой переменной создает две переменных, ссылающихся на один и тот же массив. Для копирования массивов предусмотрена реализация массивами интерфейса ICloneable.Метод Clone(), определенный в этом интерфейсе, создает неглубокую (shallow) копию массива.
Если элементы массива относятся к типу значений, как в следующем сегменте кода, то все они копируются, как показано на рис. 6.5.
int[] intArray1 = {1, 2);
int[] intArray2 = (int[])intArrayl.Clone();
Если массив содержит элементы ссылочных типов, то сами эти элементы не копируются, а копируются лишь ссылки на них.
Рисунок 6.5 - Копирование массива с элементами типа значений
На рис. 6.6 показаны переменные beatles и beatlesClone, причем вторая создана методом Clone() из beatles.Объекты Person,на которые ссылаются beatlesи beatlesClone,одни и те же. Если вы измените свойство элемента, относящегося к beatlesClone,то тем самым измените объект, относящийся и к beatles.
Person[] beatles = {
new Person {FirstName="John", LastName=''Lennon"},
new Person { FirstName="Paul", LastName="McCartney"}
} ;
Person[] beatlesClone = (Person[])beatles.Clone();
Вместо метода Clone() можно также применять метод Array.Сору(), тоже создающий поверхностную копию. Но между Clone() и Сору() есть одно важное отличие: Clone() создает новый массив, а Сору() требует наличия существующего массива той же размерности с достаточным количеством элементов.
Если нужно глубокое копирование массива, содержащего ссылочные типы, придется выполнить итерацию по объектам исходного массива с созданием новых объектов.
Рисунок 6.6 - Копирование массива с элементами ссылочного типа
Сортировка
В классе Array реализован алгоритм быстрой сортировки (Quicksort) элементов массива. Метод Sort() требует от элементов реализации интерфейса IComparable.Простые типы, такие как System.Stringи System.Int32,реализуют IComparable,так что можно сортировать элементы, относящиеся к этим типам.
В следующем примере программы создается массив names,содержащий элементы типа string,и этот массив может быть отсортирован:
string [] names = {
"Christina Aguillera",
"Shakira",
"Beyonce",
"Gwen Stefani"
};
Array.Sort(names);
foreach (var name in names)
{
Console.WriteLine(name);
}
Вывод этого кода показывает отсортированное содержимое массива:
Beyonce
Christina Aguillera
Gwen Stefani
Shakira
Если вы используете с массивом собственные классы, то должны реализовать интерфейс IComparable. В этом интерфейсе определен единственный метод CompareTo(), который должен возвращать 0, если сравниваемые объекты эквивалентны, значение меньше 0, если данный экземпляр должен следовать перед объектом, переданным в параметре, и значение больше 0, если экземпляр должен следовать за объектом, переданным в параметре.
Изменим класс Personтак, чтобы он реализовывал интерфейс IComparable< Person>.Сравнение будет выполняться по значению LastName.Поскольку LastNameимеет тип string,а в классе Stringуже реализован интерфейс IComparable,можно положиться на его реализацию метода CompareTo(). Если значения LastNameсовпадают, то сравниваются значения FirstName.
public class Person: IComparable<Person>
{
public int CompareTo(Person other)
{
if (other == null) throw new ArgumentNullException("other");
int result = this.LastName.CompareTo(other.LastName);
if (result == 0)
{
result = this.FirstName.CompareTo( other.FirstName);
}
return result;
}
//. . .
Теперь можно отсортировать массив объектов Person по значению фамилии (LastName):
Person[] persons = {
new Person { FirstName="Daraon", LastName="Hill"),
new Person { FirstName="Niki", LastName="Lauda"),
new Person { FirstName="Ayrton", LastName="Senna"),
new Person { FirstName="Graham", LastName=”Hill”)
};
Array.Sort(persons);
foreach (var p in persons)
{
Console.WriteLine(p);
}
Вывод отсортированного массива элементов Personвыглядит следующим образом:
Damon Hill
Graham Hill
Niki Lauda
Ayrton Senna
Если объекты Personпонадобится отсортировать как-то иначе, либо если нет возможности изменить класс, используемый в качестве элемента массива, то можно реализовать интерфейс IComparerили IComparer<T>.Эти интерфейсы определяют метод Compare().Один из этих интерфейсов должен быть реализован классом, подлежащим сравнению. Интерфейс IComparerнезависим от сравниваемого класса. Вот почему метод Compare() принимает два аргумента, которые подлежат сравнению. Возвращаемое значение подобно тому, что возвращает метод СошрагеТо() интерфейса IComparable.
Класс PersonComparer реализует интерфейс IComparer<Person> для сортировки объектов Person либо по FirstName,либо по LastName.Перечисление PersonCompareTypeопределяет различные опции сортировки, которые доступны в PersonComparer:FirstNameи LastName.Способ сравнения определяется конструктором класса PersonComparer,в котором устанавливается значение PersonCompareType.Метод Compare() реализован с оператором switchдля сравнения либо по LastName,либо по FirstName.
public enum PersonCompareType
{
FirstName,
LastName
}
public class PersonComparer: IComparer<Person>
{
private PersonCompareType compareType;
public PersonComparer(PersonCompareType compareType)
{
this.compareType = compareType;
}
public int Compare(Person x, Person y)
{
if(x == null) throw new ArgumentNullException("x") ;
if(y == null) throw new ArgumentNullException("y");
switch (compareType)
{
case PersonCompareType.Firstname:
return x.Firstname.CompareTo(y.FirstName);
case PersonCompareType.Lastname:
return x.Lastname.CompareTo(y.LastName);
default:
throw new ArgumentException(
"недопустимый тип для сравнения");
}
}
}
Теперь можно передавать объект PersonComparerв качестве второго аргумента метода Array.Sort().
Ниже показано, каким образом персоны сортируются по имени:
Array.Sort(persons, new PersonComparer(PersonCompareType.Firstname));
foreach (var p in persons)
{
Console.WriteLine(p);
}
В результате получится список лиц, отсортированных по имени:
Ayrton Senna
Damon Hill
Graham Hill
Niki Lauda
Класс Array также предлагает методы Sort, требующие в качестве аргумента делегата. В этом аргументе можно передавать метод, выполняющий сравнение двух объектов, вместо того, чтобы полагаться на интерфейсы IComparable или IComparer. Использование делегатов обсуждается в разделе 6.8.