Опубликован: 15.09.2010 | Доступ: свободный | Студентов: 4806 / 630 | Оценка: 3.97 / 3.80 | Длительность: 14:45:00
Лекция 9:

Интерфейсы. Контейнерные классы

Класс ArrayList

Основным недостатком обычных массивов является то, что объем памяти, необходимый для хранения их элементов, должен быть выделен до начала работы с массивом. Класс ArrayList позволяет программисту не заботиться о выделении памяти и хранить в одном и том же массиве элементы различных типов.

По умолчанию при создании объекта типа ArrayList строится массив из 16 элементов типа object. Можно задать желаемое количество элементов в массиве, передав его в конструктор или установив в качестве значения свойства Capacity, например:

ArrayList arr1 = new ArrayList();          // создается массив из 16   элементов
ArrayList arr2 = new ArrayList(1000);  // создается массив из 1000 элементов
ArrayList arr3 = new ArrayList(); 
arr3.Capacity = 1000;                        // количество элементов задается

Класс ArrayList реализован через класс Array, то есть содержит закрытое поле этого класса. Поскольку все типы в C# являются потомками класса object, массив может содержать элементы произвольного типа. Даже если в массиве хранятся обычные целые числа, то есть элементы значимого типа, внутренний класс является массивом ссылок на экземпляры типа object, которые представляют собой упакованный тип-значение. Соответственно, при занесении в массив выполняется упаковка, а при извлечении — распаковка элемента.

Если при добавлении элемента в массив оказывается, что фактическое количество элементов массива превышает его емкость, она автоматически удваивается, то есть происходит повторное выделение памяти и переписывание туда всех существующих элементов. Пример занесения элементов в экземпляр класса ArrayList:

arr1.Add( 123 );    arr1.Add( -2 );    arr1.Add( "Вася" );

Доступ к элементу выполняется по индексу, однако при этом необходимо явным образом привести полученную ссылку к целевому типу, например:

int    a = (int) arr1[0];
int    b = (int) arr1[1];
string s = (string) arr1[2];

Попытка приведения к типу, не соответствующему хранимому в элементе, вызывает генерацию исключения InvalidCastException.

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

Недостатком этого решения является то, что для каждого метода стандартной коллекции приходится описывать метод-оболочку, вызывающий стандартный метод. Хотя это и несложно, но несколько неизящно. В C#, начиная с версии 2.0, появились классы-прототипы (generics), позволяющие решить эту проблему.

Классы-прототипы (generics)

Классы-прототипы (generics) — это классы, имеющие в качестве параметров типы данных. Чаще всего их применяют для хранения данных, то есть в качестве контейнерных классов, или коллекций. Во вторую версию библиотеки .NET добавлены параметризованные коллекции для представления основных структур данных, применяющихся при создании программ — стека, очереди, списка, словаря и т. д. Эти коллекции, расположенные в пространстве имен System.Collections.Generic, дублируют аналогичные коллекции пространства имен System.Collections. В таблице 9.4 приводится соответствие между обычными и параметризованными коллекциями библиотеки .NET (параметры, определяющие типы данных, хранимых в коллекции, указаны в угловых скобках).

Таблица 9.4. Параметризованные коллекции библиотеки .NET версии 2.0
Класс-прототип (версия 2.0) Обычный класс
Comparer<T> Comparer
Dictionary<K,T> HashTable
LinkedList<T>
List<T> ArrayList
Queue<T> Queue
SortedDictionary<K,T> SortedList
Stack<T> Stack<T>

В качестве примера рассмотрим применение универсального "двойника" класса ArrayList — класса List<T> — для хранения коллекции объектов известных нам классов Monster и Daemon, а также для хранения целых чисел.

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    using MonsterLib;    // библиотека, в которой хранятся классы Monster и Daemon
    class Program
    {
        static void Main()
        {
            List<Monster> stado = new List<Monster>();
            stado.Add( new Monster( "Monia" ) );
            stado.Add( new Monster( "Monk" ) );
            stado.Add( new Daemon ( "Dimon", 3 ) );

            foreach ( Monster x in stado ) x.Passport();

            List<int> lint = new List<int>();
            lint.Add( 5 ); lint.Add( 1 ); lint.Add( 3 );
            lint.Sort();
            int a = lint[2];
            Console.WriteLine( a );

            foreach ( int x in lint ) Console.Write( x + " ");
}}}
Листинг 9.7. Использование универсальной коллекции List<T>

Результат работы программы:

Monster Monia    health = 100 ammo = 100
Monster Monk     health = 100 ammo = 100
Daemon Dimon     health = 100 ammo = 100 brain = 3
5
1 3 5

В листинге 9.7 две коллекции. Первая (stado) содержит элементы пользовательских классов, которые находятся в библиотеке MonsterLib.dll. В коллекции, для которой объявлен тип элементов Monster, благодаря полиморфизму можно хранить элементы любого производного класса, но не элементы других типов. Достоинством такого ограничения является то, что компилятор может выполнить контроль типов, что повышает надежность программы и упрощает поиск ошибок.

Коллекция lint состоит из целых чисел, причем для работы с ними не требуются ни операции упаковки и распаковки, ни явные преобразования типа при получении элемента из коллекции.

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

Вопросы и задания для самостоятельной работы студента

  1. Изучите разделы стандарта C#, касающиеся интерфейсов.
  2. Изучите по справочной системе основные стандартные интерфейсы .NET.
  3. Чем интерфейс отличается от абстрактного класса?
  4. Должен ли класс реализовывать все методы всех своих интерфейсов-предков?
  5. Какие операции используются для проверки, реализует ли класс заданный интерфейс?
  6. Перечислите стандартные интерфейсы .NET, которые определяют возможности сортировки, клонирования и просмотра объектов с помощью оператора foreach.
  7. Опишите, как используется конструкция yield.
  8. Изучите по справочной системе состав пространства имен System.Collections.
  9. Изучите по справочной системе свойства и методы класса ArrayList.
  10. Изучите разделы стандарта C#, касающиеся классов-прототипов.
  11. Изучите по справочной системе состав пространства имен System.Collections.Generic. Опишите основные виды абстрактных структур данных.
  12. Допускает ли структура "линейный список" прямой доступ к своим элементам?
  13. В чем основная задача хеш-функции?
  14. В каких задачах используется стек? Приведите примеры.
  15. Сравните время поиска элемента в линейном списке и в дереве поиска.
  16. В чем преимущество массива перед другими структурами данных?
  17. Опишите состав пространства имен System.Collections и дайте характеристику основных типов-коллекций.
  18. Какие новые контейнеры появились в библиотеке классов для версии С# 2.0?
  19. В чем преимущества контейнеров версии С# 2.0?
  20. Какие методы содержит класс ArrayList?

Лабораторная работа 10. Интерфейсы и параметризованные коллекции

Выполнить задания лабораторной работы "Наследование классов", используя для хранения экземпляров разработанных классов стандартные параметризованные коллекции. Во всех классах реализовать интерфейс IComparable и перегрузить операции отношения для реализации значимой семантики сравнения объектов по какому-либо полю на усмотрение студента.

Stefan Berzan
Stefan Berzan
Молдова, Кишинев
Дмитрий Казимиров
Дмитрий Казимиров
Россия, Омск