Использование ссылочных типов

В дополнение к возможности объявлять массивы предопределённых типов, можно так­же объявлять массивы специальных пользовательских типов. Начнем с класса 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,также находящийся в управляемой куче.

Использование ссылочных типов - student2.ru

Рисунок 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.

Использование ссылочных типов - student2.ru

Рисунок 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 демонстрируется отличие обычного двумерного массива от зубчатого. Показанный здесь зубчатый массив содержит три строки, причем первая строка имеет два элемента, вторая - шесть элементов, а третья - три элемента.

Использование ссылочных типов - student2.ru

Рис. 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();

Если массив содержит элементы ссылочных типов, то сами эти элементы не копируют­ся, а копируются лишь ссылки на них.

Использование ссылочных типов - student2.ru

Рисунок 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() создает новый массив, а Сору() требует наличия существующего массива той же размерности с достаточным количеством элементов.

Если нужно глубокое копирование массива, содержащего ссылочные типы, при­дется выполнить итерацию по объектам исходного массива с созданием новых объектов.

Использование ссылочных типов - student2.ru

Рисунок 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.

Наши рекомендации