Шаблоны
Общее представление
В 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.