Главная » C# » Управление коллекциями до С# 2.0

0

До С# 2.0 основные классы коллекций находились в пространстве имен System.Collections. Далее приводится список некоторых классов и интерфейсов данного  пространства  имен.

• ArrayList — общая коллекция, управляющая всеми объектами, на которые имеются ссылки, с помощью внутреннего массива. Данный класс решает прлему  с увеличением  размера  массива.

П HashTabie — коллекция, в которой отдельные объекты хранятся в виде пар "ключ/значение".  В  предыдущей  главе  для  получения  комнатной  группировки по ее идентификатору применялся индексатор. То же самое можно было  бы Сделать С ПОМОЩЬ Ю КОЛЛеКЦИИ HashTabie.

• icollection— интерфейс,  реализуемый  классом  ArrayList и  предоставляий базовую функциональность,  которая  копирует все элементы  в другой массив.

• iDictionary— интерфейс, реализуемый классом HashTabie и позволяющий ассоциировать  ключ  со  значением.

• iList — интерфейс, реализуемый классом ArrayList и предоставляющий мехизм  общего доступа для  манипулирования  коллекцией  элементов.

• Queue — коллекция, реализующая механизм FIFO (First In — First out, первым пришел — первым обслужен, очередь). Данный класс можно использовать для обработки набора инструкций. Инструкция, которую необходимо  обработать первой,  будет добавлена  в коллекцию  первой.

•   stac k — коллекция, реализующая механизм LIFO (Last  In —  Last  Out,  послеим  пришел —  первым  обслужен,  стек).  Данный   класс  можно   рассматривать как  стопку   листов   бумаги,   из   которой   первым   снимается   лист,   положенный в  нее  последним.

Все ТИПЫ коллекций — ArrayList, HashTabie, Queue И Stack— реализуют способ для  хранения  набора  типов.  Разница  между  этими  типами  коллекций  заключается в том, каким образом отдельные объекты хранятся и извлекаются из коллекции. Примеры использования разных типов коллекций  приводятся  в  разд.  "Дополнельные   сведения   о   типах  коллекций"  далее   в  этой  главе.

Простой пример коллекции

Рассмотрим шаг за шагом пример использования коллекции в стиле до С# 2.0. Соайте новое консольного приложение; назовите его oneToManySampies. Потом добавьте   к   проекту   новый   класс.   Для   этого   щелкните   правой   кнопкой   мыши

по названию проекта в Solution Explorer и выберите команду меню Add | Class | Class. Присвойте ему имя Example.cs и вставьте в него следующий код:

using System.Collections;

class Example { int _value;

public int Value { get {

return _value;

}

set {

_value = value;

}

}

}

static class Tests {

static void PlainVanillaObjects() {

IList objects = new ArrayList();

objects.Add(new Example { Value =1 0 }); objects.Add(new Example { Value = 20 });

foreach (Example obj in objects) { Console.WriteLine("Object value (" + obj.Value + ")");

}

}

public static void RunAllO { PlainVanillaObjects();

}

}

Это тип кода, применяемый до версии С# 2.0; при его написании выполняется стаартная последовательность шагов:

1. Определяется пользовательский тип. В данном примере название типа — Example.

2. Создаются  и  добавляются  в  коллекцию  экземпляры  пользовательского  типа.

В  примере  в  коллекцию  типа  ArrayList добавляются  два  экземпляра  класса

Example.

3. Выполняется манипулирование коллекцией, чтобы получить доступ для работы с пользовательскими типами. В примере коллекция ArrayList является экземяром интерфейса IList.

Жирным шрифтом в примере выделен код, выполняющий основные действия. Соанием экземпляра типа Array реализуется администратор коллекции. После этого экземпляр ArrayList присваивается переменным объектов типа iList. Интерфейс iList позволяет использовать коллекцию в контексте компонентно-ориентированной среды разработки. Чтобы добавить в коллекцию два объекта, дважды  вызывается метод Add (). Для прохождения в цикле по  элементам коллекции применяется опатор  foreach.

ПРИМЕЧАНИЕ

То обстоятельство, что классы коллекции можно использовать в контексте компонено-ориентированного приложения, не является случайностью. Когда в Microsoft создали библиотеку .NET, компонентам в ней отводилась одна из главных ролей.

Чтобы  исполнить тесты,  откорректируйте  файл  Program.es  следующим  образом:

class Program

{

static void Main(string!] args)

{

Tests.RunAll();

}

}

Для  запуска  программы  нажмите  комбинацию  клавиш  <Ctrl>+<F5>.

Проблема со смешанными типами

В коде примера уникальным является то, что оператор  foreach в самом  деле рабает должным образом и знает, что объекты в  коллекции  принадлежат  к  типу Example. Но  следующий  код  вставляет  в  коллекцию  объект,  который  вызовет сбой в работе  цикла:

class Another { }

IList objects = new ArrayListO; objects.Add(new Example { Value = 10 }}; objects.Add(new Example { Value =2 0 }); objects.Add(new Another());

foreach (Example obj in objects) { Console.WriteLine("Object value (" + obj.Value + ")");

}

Жирным шрифтом выделен код, иллюстрирующий,  что  объект  коллекции  содеит два экземпляра типа Example и один экземпляр типа Another. Данный код скомпилируется  без  ошибок,  таким  образом,  вводя  нас  в  заблуждение,  что  с  ним

все в порядке. Но при попытке выполнить приложение (в обычном или отладочном режиме) будет выведено следующее сообщение:

Unable to cast object of type ‘Another’ to type ‘Example’.1

Что же теперь,  применять  в  коллекции  несколько типов?  Аргументы  имеются за и против такого решения, но проблема не заключается в возможности смешивания типов, а в том, что их можно смешивать, даже когда это не входит в намерения разработчика.

Использование ключевого слова foreach со смешанными типами вызовет искление, т. к. в каждой итерации объект коллекции приводится к типу Example. Так как последний элемент коллекции имеет тип Another, приведение будет неуспеым, что и вызывает исключение. До .NET 2.0 в коллекциях нельзя было принуать непротиворечивость типов, и это было проблемой.

Для смешанных типов правильным циклом foreach был бы следующий:

foreach (object obj in objects) { if (obj is Example) {

//  …

}

else if (obj is Another) {

I I . . .

}

}

Проблема с обычными типами

Другой проблемой с коллекциям в более ранних версиях С#, чем версия 2.0, явлтся низкая производительность. Для примера  рассмотрим следующий  код,  котый манипулирует обычными типами:

IList objects = new ArrayListO; objects.Add(l);

objects.Add(2);

foreach (int val in objects) { Console.WriteLine("Value (" + val + ")");

}

В примере опять создается экземпляр ArrayList, но на этот раз в коллекцию давляются числа 1 и 2. Потом эти числа обрабатываются в цикле foreach. Хотя этот код и работает, в нем имеется не сразу видимый аспект, отрицательно влияий на производительность. В  коллекцию добавляются значения  обычного типа, что означает манипулирование памятью стека.

Но определение IList использует объекты:

public interface IList : ICollection, IEnumerable

{

1   Невозможно привести объект типа Another к типу Example.

// Методы

int Add(object value); void Clear() ;

bool Contains(object value); int IndexOf(object value);

void Insert(int index, object value); void Remove(object value);

void RemoveAt(int index);

// Свойства

bool IsFixedSize { get; } bool IsReadOnly { get; }

object this[int index] { get; set; }

}

Способ определения iLis t и обычного типа должен настораживать. Так как объект является ссылочным типом, у нас имеется конфликт: в iLis t хранятся ссылочные типы, но in t является обычным типом.

Здесь среда  .NET знает о конфликте  и исправляет его. Не следует рассматривать это исправление как решение на скорую руку для данной проблемы, а как дейсие, в котором принимают участие все среды виртуальных машин, подобных .NET. В среде .NET для выражения преобразования ссылочного типа в обычный и обрао применяются термины "упаковка" (boxing) и "распаковка" (unboxing) соответвенно.

Для облегчения понимания идей упаковки и распаковки рассмотрим пример. Дустим, что вы создаете список, обращающийся к обычным типам. Массив являея ссылочным типом, хранящимся в куче,  в то время как обычные типы хранятся в стеке. Если массив будет обращаться к данным в стеке, то это вызовет проблему совместимости типов. Таким образом, нам необходимо переместить память со ста в кучу, но это бы нарушило принцип, лежащий в основе обычных типов. Решием является компромисс в виде упаковки и распаковки.

Чтобы проиллюстрировать, что происходит при упаковке, я написал код, работаий подобно операции упаковки обычного типа. Разница состоит в том, что в коде операция выполняется явно, а при упаковке — неявно.

class ReferenceHeap { public int Value;

}

public static void MethodO { int onStack = 1;

ReferenceHeap onHeap = new ReferenceHeap {Value = onStack};

}

В данном примере в методе Method*) объявляется переменная onstack обычного типа, память для которого выделяется в контексте метода, т. е. в стеке. Тип ReferenceHeap является классом и поэтому ссылочным типом; соответственно, все его данные автоматически сохраняются в куче. Когда объявляется и инициализиртся   переменная   опнеар, значение  переменной   onstack перемещается   в   кучу и присваивается экземпляру опнеар. То же самое происходит и при выполнении операции упаковки, но только автоматически и прозрачно для пользователя. При работе со списками в версиях, предшествующих С# 2.0, все обычные типы автомически упаковываются и распаковываются.

ПРИМЕЧАНИЕ

Важно помнить, что при выполнении упаковки и распаковки перемещаются значения. Поэтому, при изменении значения переменной  onstack, значение  переменной опнеар не изменится.

При распаковке значение перемещается с кучи в стек, что в случае с нашим примом означает перемещение значения из переменной опнеар в переменную onstack.

Упаковка и распаковка выполняются автоматически, но за это приходится расплачаться понижением производительности, т. к. выполняются операции выделения пяти и присваивания значений.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии