Шаблоны
Общее представление
В C# можно объявлять шаблоны классов и шаблоны функций. Шаблоны классов и шаблоны функций – две разные вещи, которые в общем случае могут сосуществовать в рамках одного и того же объявления.
Объявление любого шаблона предполагает использование специальных обозначений, которые называются параметрами шаблона. Традиционно для этого применяются однобуквенные обозначения: A, ..., K, L, M, N, ..., T, ..., Z.
При объявлении шаблонного класса или функции параметры шаблона заменяются на конкретные имена классов.
Шаблонный класс – это класс, который строится на основе ранее объявленного шаблона для определенного класса (этот класс мы будем называть подстановочным классом). Шаблонный класс (и функция) обеспечивают стандартную реализацию какой-либо функциональности для подстановочного класса — например, построения специализированной коллекции для множества объектов данного типа.
Параметры шаблонов позволяют параметризовать шаблоны классов, структур, интерфейсов, делегатов и функций классами, для которых эти шаблоны создавались с целью расширения их функциональности.
Ниже приводится пример объявления шаблона класса Stack с параметром типа <T>.
Параметр T в объявлении рассматриваемого шаблонного класса обозначает место подстановки реального типа. Он используется для обозначения типа элемента для внутреннего массива элементов, типа параметра метода Push и типа результата для метода Pop:
public class Stack<T>
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}Вместо преобразований в и из класса object, параметризованные коллекции ( Stack<T> в том числе) принимают на сохранение значения типа, для которого они созданы, и сохраняют данные этого типа без дополнительных преобразований. При использовании шаблонного класса Stack<T>, вместо T подставляется реальный тип. В следующем примере int задается как аргумент типа (type argument) для T:
Stack<int> stack = new Stack<int>(); stack.Push(3); int x = stack.Pop();
Тип Stack<int> называют составным или шаблонным типом (constructed type). При объявлении этого типа каждое вхождение T заменяется аргументом типа int.
Когда объект – представитель класса Stack<int> создается, массив items оказывается объектом типа int[], а не object[].
Методы Push и Pop класса Stack<int> также оперируют значениями int, при этом помещение в стек значений другого типа приводит к ошибкам компиляции (а не ошибкам времени выполнения).
Также не требуется явного приведения извлеченных из коллекции значений к их исходному типу.
Шаблоны обеспечивают строгий контроль типов. Это означает, например, что помещение int в стек объектов XXX является ошибкой.
Точно так же, как Stack<int> ограничен возможностью работы только со значениями типа int, так и Stack<XXX> ограничен объектами типа XXX.
А компиляция последних двух строк следующего примера выдаст ошибку:
Stack<XXX> stack = new Stack<XXX>(); stack.Push(new XXX()); XXX xxx = stack.Pop(); stack.Push(3); // Ошибка несоответствия типов int x = stack.Pop(); // Ошибка несоответствия типов
Объявление шаблона может содержать любое количество параметров типа. В приведенном выше примере в Stack<T> есть только один параметр типа, но шаблонный класс Dictionary должен иметь два параметра типа: один для подстановочного типа ключей, другой для подстановочного типа значений:
public class Dictionary<K,V>
{
public void Add(K key, V value) {...}
public V this[K key] {...}
}При объявлении шаблонного класса на основе шаблона Dictionary<K,V> должны быть заданы два аргумента типа:
Dictionary<string,XXX> dict = new Dictionary<string,XXX>();
// В словарь добавляется объект типа XXX,
// проиндексированный строкой x1
dict.Add("x1", new XXX());
// Извлечение из словаря элемента, проиндексированного строкой "x1"
XXX xxx = dict["x1"];Пример использования шаблонов: сортировка.
Старая задача, новые решения с использованием предопределенных шаблонов классов и интерфейсов...
using System;
using System.Collections;
using System.Collections.Generic;
namespace PatternArrays
{
// Данные для массива элементов.
// Подлежат сортировке в составе шаблонного массива методом Sort.
class Points
{
public int x;
public int y;
public Points(int key1, int key2)
{
x = key1;
y = key2;
}
// Вычисляется расстояние от начала координат.
public int R
{
get
{
return (int)(Math.Sqrt(x * x + y * y));
}
}
}
// ...ШАБЛОННЫЙ КОМПАРЕР на основе шаблона интерфейса...
class myComparer : IComparer<Points>
{
// Предлагаемый ШАБЛОННЫЙ метод сравнения возвращает разность расстояний
// двух точек (вычисляется по теореме Пифагора) от начала координат
// – точки с координатами (0,0). Чем ближе точки к началу координат
// – тем они меньше. Не требуется никаких явных приведений
типа.
// Шаблон настроен на работу с классом Points.
int IComparer<Points>.Compare(Points p1, Points p2)
{
return (p1.R - p2.R);
}
}
// После реализации соответствующего ШАБЛОННОГО интерфейса объект-
// КОМПАРЕР обеспечивает реализацию стандартного алгоритма сортировки.
class Class1
{
static void Main(string[] args)
{
// Объект - генератор "случайных" чисел.
Random rnd = new Random();
int i;
// Очередь Points.
Queue<Points> qP = new Queue<Points>();
// Шаблонный перечислитель. Предназначен для обслуживания
// шаблонной очереди элементов класса Points.
IEnumerator<Points> enP;
// Сортировка поддерживается классом Array.
Points[] pp;
// Создали Компарер, способный сравнивать пары
// объектов - представителей класса Points.
myComparer c = new myComparer();
Console.WriteLine("========================================");
// Проинициализировали массив объектов - представителей класса Points.
for (i = 0; i < 10; i++)
{
qP.Enqueue(new Points(rnd.Next(0, 10), rnd.Next(0, 10)));
}
enP = ((IEnumerable<Points>)(qP)).GetEnumerator();
for (i = 0; enP.MoveNext(); i++)
{
Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y);
}
// Сортируются элементы массива типа Points, который формируется на
// основе шаблонной очереди.
// Условием успешной сортировки элементов массива является реализация
// интерфейса IComparer. Если Компарер не сумеет справиться с
// поставленной задачей – будет возбуждено исключение.
// На основе очереди построили массив.
pp = qP.ToArray();
// А саму очередь можно почистить!
qP.Clear();
try
{
Array.Sort<Points>(pp, c);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
// Сортировка произведена, очередь восстановлена.
for (i = 0; i < 10; i++) qP.Enqueue(pp[i]);
Console.WriteLine("========================================");
enP = ((IEnumerable<Points>)(qP)).GetEnumerator();
for (i = 0; enP.MoveNext(); i++)
{
Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y);
}
Console.WriteLine("========================================");
}
}
}
Листинг
13.1.