Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык C# и основы платформы .NET.docx
Скачиваний:
36
Добавлен:
11.05.2015
Размер:
178.68 Кб
Скачать

18. Универсальные шаблоны

Универсальные шаблоны(generics) позволяют при разработке пользовательского типа или метода указать в качестве параметра тип, который конкретизируется при использовании. Универсальные шаблоны применимы к классам, структурам, интерфейсам, делегатам и методам.

18.1. Универсальные классы и структуры

Поясним необходимость универсальных шаблонов на следующем примере. Пусть разрабатывается класс для представления структуры данных «стек». Чтобы не создавать отдельные версии стека для хранения данных определённых типов, программист выбирает базовый тип objectкак тип элемента:

publicclassStack

{

privateobject[] _items;

publicvoidPush(objectitem) { . . . }

public object Pop() { . . . }

}

Класс Stackможно использовать для разных типов данных:

varstack =newStack();

stack.Push(newCustomer());

Customerc = (Customer)stack.Pop();

varstack2 =newStack();

stack2.Push(3);

inti = (int)stack2.Pop();

Однако универсальность класса Stackимеет и отрицательные моменты. При извлечении данных из стека необходимо выполнять приведение типов. Для типов значений (например,int) при помещении данных в стек и при извлечении выполняются операции упаковки и распаковки, что отрицательно сказывается на производительности. И, наконец, неверный тип помещаемого в стек элемента может быть выявлен только на этапе выполнения, но не компиляции.

var stack = new Stack(); // планируем сделать стек чисел

stack.Push(1);

stack.Push(2);

stack.Push("three"); // вставили не число, а строку

varsum = 0;

for(vari = 0; i < 3; i++)

{

// код компилируется, но при выполнении на третьей итерации

// будет сгенерирована исключительная ситуация

sum += (int)stack.Pop();

}

Необходимость устранения описанных недостатков явилась основной причиной появления универсальных шаблонов, представленных в C# 2.0.

Опишем класс Stackкак универсальный тип. Для этого используется следующий синтаксис: после имени класса в угловых скобках указываетсяпараметр типа. Этот параметр может затем использоваться при описании элементов класса (в нашем примере – методов и массива) на месте указания на тип.

publicclassStack<T>

{

privateT[] _items;

publicvoidPush(T item) { . . . }

public T Pop() { . . . }

}

Использовать универсальный тип «как есть» в клиентском коде нельзя, так как он является не типом, а, скорее, «чертежом» типа. Для работы со Stack<T>необходимо использоватьсконструированный тип(constructedtype), указав в угловых скобках аргумент-тип. Аргумент-тип может быть любым типом. Можно создать любое количество экземпляров сконструированных типов и каждый из них может использовать разные аргументы типа.

Stack<int> stack = newStack<int>();

stack.Push(3);

intx = stack.Pop();

Обратите внимание: при работе с типом Stack<int>отпала необходимость в выполнении приведения типов при извлечении элементов из стека. Кроме этого, теперь компилятор отслеживает, чтобы в стек помещались только данные типаint. И ещё одна особенность: нет необходимости в упаковке и распаковке типа значения, а это приводит к росту производительности.

Подчеркнём некоторые особенности сконструированных типов. Во-первых, сконструированный тип не связан отношением наследования с универсальным типом. Во-вторых, даже если классы AиBсвязаны наследованием, сконструированные типы, в которыхAиBявляются аргументами-типами, этой связи лишены. В-третьих, статические поля и статический конструктор, описанные в универсальном типе, уникальны для каждого сконструированного типа.

При объявлении универсального шаблона можно использовать несколько параметров-типов. Приведём фрагмент описания класса для хранения пар «ключ-значение» с возможностью доступа к значению по ключу:

publicclassDictionary<K, V>

{

publicvoidAdd(K key, V value) { . . . }

publicVthis[K key] { . . . }

}

Сконструированный тип для Dictionary<K, V>должен быть основан на двух аргументах-типах:

Dictionary<int,Customer> dict =newDictionary<int,Customer>();

В языке C# существует операция default, которая возвращает значение по умолчанию для переменной указанного типа. Эта операция может использоваться в тех методах, где возвращаемое значение задано как параметр типа:

publicclassCache<K, V>

{

// метод для поиска элемента по ключу

publicV LookupItem(K key)

{

// если элемент не найден, вернём значение по умолчанию

returnContainsKey(key) ? GetValue(key) :default(V);

}

}