
CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky
.pdf▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌◄
Обратите внимание, что событийный делегат SortHandler и переменная
события onSort, должны быть одинаково доступны в месте подписки на событие.
При генерации события целесообразно проверять значение переменной
события. Эта переменная остаётся равной |
null, если на событие нет ни одной |
подписки.
Обратите внимание на тот факт, что генерация события "событие:генерация
события" , в отличие от генерации исключения, оформляется как обращение к
методу. Тем самым после обработки события управление автоматически
возвращается в точку, непосредственно следующую за оператором генерации
события.
Контрольные вопросы
В чём основное назначение делегата? Назовите этапы применения делегатов.
Члены каких видов могут присутствовать в делегате-типе? Объясните назначение элементов объявления делегата-типа. Как объявить ссылку с типом делегата?
Как создать экземпляр делегата?
Как аргументы можно использовать при обращении к конструктору делегата? Где может размещаться объявление делегата-типа?
Каковы возможности свойств Method и Target? Для чего применяются массивы делегатов? Что такое многоадресный экземпляр делегата?
Какие средства поддерживают работу с многоадресными экземплярами делегатов?
Как получить массив делегатов из многоадресного делегата? Что такое механизм обратного вызова?
Как используются делегаты для организации обратных вызовов? Что такое анонимный метод?
Как специфицируется сигнатура анонимного метода?
Приведите пример размещения анонимного метода в обращении к методу, требующему обратных вызовов.
Что такое событие в языке C# ?
Объясните синтаксис оператора посылки сообщения. Приведите формат объявления события.
Что такое переменная события?
Что определяет делегат, указанный в объявлении события? Какие действия предусматривает подписка на события? Назовите этапы работы с событиями.
Глава 18. Обобщения
18.1. Обобщения как средство абстракции
Код, записанный на каком-либо языке программирования, представляет собой
наиболее детализированное и поэтому в общем случае сложное представление алгоритма, выбранного для решения конкретной задачи. Сложность кода объясняется тем обстоятельством, что в нём должны быть учтены все детали реализации алгоритма с помощью применяемого языка и ограничения той операционной среды, в которой должна выполняться программа. Сложность кода и трудоёмкость его разработки делают задачи его повторного использования и автоматизации его генерации весьма важными и актуальными. В предыдущих главах рассмотрены средства, позволяющие программировать на основе объектно-
ориентированного подхода. Этот подход позволяет создавать отдельные классы и библиотеки классов, пригодные для повторных использований. Применяя библиотеку классов, можно существенно снизить трудоёмкость разработки
программ, абстрагируясь от деталей кода, скрытых (инкапсулируемых) в
используемых объектах библиотечных классов. |
|
Следующий уровень абстракции – параметризация |
"параметризация:типов" |
объявлений типов (например, классов) и методов |
"параметризация:методов" . |
Прежде чем переходить к описанию этого механизма (реализованного в языке C# с
помощью обобщений) поясним на примере его основные принципы.
Предположим, что для дальнейшего применения необходимо иметь класс,
объект которого представляет собой отдельный элемент связного списка, каждый из
которых хранит значения типа int. class ListInt {
public int data; public ListInt (int d) { data = d;}
…
}
Если в дальнейшем потребуется класс элементов связного списка, объекты
которого хранят значения типа char, то придётся объявлять его, например, так: class ListChar {
public char data; public ListInt (char d) { data = d;}
…
}
По структуре класс ListInt и ListChar подобны, единственное отличие – типы
полей data и типы параметров конструкторов в одном случае int, а во втором char.
Этот факт, а именно различие классов только в обозначениях типов, делает возможным создание обобщённого (параметризованного) класса, который будет служить шаблоном для автоматической генерации его частных случаев – классов с полями типов int, char, а при необходимости long, double и т.д.
В языке C# такой механизм автоматизированной генерации кодов конкретных
объявлений существует. Определения (объявления) классов, структур,
интерфейсов, делегатов и методов могут быть параметризованы типами тех данных,
которые представлены этими конструкциями или обрабатываются с их помощью. О
такой возможности параметризации объявлений говорят, используя термин
"обобщения". "обобщения" Механизм обобщений языка C# схож с механизмом шаблонов (templates) классов и функций языка С++. Кроме того, как указывает стандарт C#, обобщения хорошо знакомы программистам, владеющим языками Эффель или Ада. Отметим, что в русскоязычной литературе, посвящённой языкам Эффель и Ада обобщённые (generic) методы называют родовыми подпрограммами,
а об обобщённом программировании говорят как о родовом программировании.
Параметризованные объявления классов и структур называют,
соответственно, объявлениями обобщённых классов |
"объявления:обобщённых |
|
классов" |
(generic class declaration) и объявлениями обобщённых структур |
|
"объявления:обобщённых структур " (generic struct |
declaration). И классы и |
структуры в этих случаях параметризованы типами своих данных и типами тех данных, которые должны обрабатываться методами этих классов и структур.
Интерфейсы могут быть параметризованы типами тех данных, которые обрабатываются методами интерфейсов после их реализации. Объявления таких
параметризованных интерфейсов называют |
объявлениями обобщённых |
|||
интерфейсов |
"объявления:обобщённых интерфейсов " |
(generic interface |
||
declaration). Для того, чтобы создавать |
"обобщённые алгоритмы" выполняют |
|||
параметризацию методов типами применяемых в них данных. Такие |
||||
параметризованные методы называют |
обобщёнными методами |
"обобщённые |
||
методы" ( g e n e r i c m e t h o d s ) . В объявлении |
обобщённого делегата |
"объявления:обобщённых делегатов " типизирующие параметры определяют типы параметров и тип возвращаемого значения тех методов, которые должны представлять экземпляры делегата.
18.2. Декларации обобщённых классов "декларация обобщённого класса"
Рассмотрим формат декларации обобщённого (параметризованного) класса:
модификаторы_классаopt class имя_класса
список_типизирующих_параметров база_классаopt
ограничения_типизирующих_параметровopt
тело_класса;opt
В приведённом формате обязательными являются: служебное слово |
class, |
||
имя_класса |
(а это, как мы знаем, |
– идентификатор) , |
|
список_типизирующих_параметров |
"типизирующие параметры:список" |
и |
|
тело_класса. Основным признаком обобщённого класса служит наличие в его |
|
объявлении списка_типизирующих_параметров. Если в объявлении опустить этот
список и, конечно, ограничения типизирующих параметров "типизирующие
параметры:ограничения" , то оно превратится в определение обычного
(непараметризованного) класса.
Список типизирующих параметров представляет собой заключённую в угловые скобки < > последовательность разделённых запятыми идентификаторов.
Обратите внимание, что обобщённый класс может быть наследником базового класса или может реализовать интерфейс, причём и базовый класс и реализуемый интерфейс, в свою очередь, могут быть параметризованными. В приведённом формате декларации обобщённого класса возможность наследования обозначена конструкцией "база_класса" "база класса" .
В стандарте C# для знакомства с обобщёнными классами предлагается
рассмотреть примерно такое объявление (программа 18_01.cs): class Stack <ItemType> // стек для любых данных
{
static int stackSize = 100; // предельный размер каждого стека private ItemType[ ] items = new ItemType[stackSize];
private int index = 0; // номер свободного элемента public void Push(ItemType data) { // поместить в стек
if (index == stackSize)
throw new ApplicationException("Стек переполнен!"); items[index++] = data;
}
public ItemType Pop() { // взять из стека if (index == 0)
throw new ApplicationException("Стек пуст!"); return items[--index];
}
}
Объявление вводит обобщённый класс с именем Stack для представления стека, то есть такой структуры данных, которая позволяет запоминать последовательно передаваемые ей значения и извлекать их в порядке, обратном их поступлению. Поступающие в такой стек значения запоминаются в одномерном массиве из 100 элементов с именем (ссылкой) items. Особенность объявления этого массива в том, что тип его элементов определяет типизирующий параметр с именем
ItemType. Для работы с элементами стека в обобщённом классе определены два метода – Push() и Pop(). Первый из них получает через параметр некоторое значение и должен поместить его в массив, связанный со ссылкой items. Метод Pop()
извлекает из массива последний из поступивших туда элементов и возвращает его значение в точку вызова. Самое важное здесь – использование типизирующего параметра ItemType. Он определяет тип элементов массива, тип параметра метода
Push() и тип возвращаемого методом Pop() значения.
Основная идея обобщений состоит в том, что декларация обобщенного класса служит шаблоном для самых разнообразных уже непараметризованных классов,
которые компилятор автоматически определяет (строит), исходя из декларации обобщённого класса и имеющихся в программе конкретных "обращений" к этой декларации. Конструкция, которую мы условно назовём "обращением к декларации
обобщённого класса", имеет вид:
имя_обобщённого_класса <список_типизирующих_аргументов>
Здесь каждый типизирующий аргумент "типизирующий аргумент " – имя
конкретного типа, которое должно заменить в объявлении обобщённого класса все вхождения соответствующего типизирующего параметра. Соответствие между типизирующими параметрами декларации обобщённого класса и типизирующими аргументами в обращении к нему устанавливается по их взаимному расположению.
Здесь используется традиционная для обращений к методам, функциям и процедурам схема замещения параметров (формальных параметров) аргументами
(фактическими параметрами).
Если в программе, содержащей декларацию обобщённого класса
Stack<ItemType>, |
использовать конструкцию Stack< char>, то это вводит |
специализированный |
(инстанцированный, конкретный) тип |
"тип:специализированный" , порождаемый из декларации обобщённого класса
Stack<ItemType>. В этом специализированном типе, который существует только
после компиляции программы, все вхождения типизирующего параметра ItemType
заменены типом char. Элементы массива, связанного со ссылкой items , будут иметь
тип char "тип:char" , метод Pop() будет возвращать символьные значения,
параметр метода Push() будет символьного типа.
Для обозначения специализированного типа стандарт C# вводит термин
"сконструированный тип "тип:сконструированный" " (constructed type). Имена сконструированных типов можно использовать как и обычные имена
непараметризованных классов. Например, так:
Stack<char> symbols=new Stack<char>(); symbols.Push('A');
char ch= symbols.Pop();
Вданном примере создан экземпляр (объект) класса Stack< char>, определена
исвязана с этим объектом ссылка symbols с типом того же класса. Затем в стек помещён символ 'A' и с помощью метода Pop() этот символ получен (извлечён) из стека.
Точно так же, как для хранения в стеке символьных данных определён класс
Stack<char>, можно вводить сконструированные типы для стеков с элементами любых типов. Это могут быть как предопределённые типы языка, так и типы,
введённые программистом. Например:
Stack<double>real = new Stack <double>(); real.Push(3.141592);
double pi=real.Pop();
Введя сконструированный тип, мы можем использовать его только для работы с теми данными, "на которые его настроен..." С учётом предыдущего объявления
переменной symbol следующий оператор не верен: symbols.Push(2.7171); // ошибка компиляции
В декларации обобщённого класса типизирующих параметров может быть несколько. Ограниченный на их количество нет. Естественное требование – число типизирующих аргументов, должно быть равно количеству типизирующих параметров в декларации обобщённого класса.
Каждый типизирующий параметр "типизирующий параметр" определяет имя в пространстве декларации своего класса. Таким образом, имя типизирующего параметра не может совпадать: с именем другого типизирующего параметра своего же класса, с именем обобщённого класса, с именем члена этого класса.
Область существования типизирующего параметра включает базу класса,
ограничения типизирующих параметров и тело класса. В отличие от членов класса типизирующие параметры не распространяются на производные классы. В области существования типизирующий параметр можно использовать как имя типа.
18.3. Ограничения типизирующих параметров
В приведённом выше формате объявления обобщённого класса указан один весьма важный раздел, который определяет ограничения, накладываемые на типизирующие аргументы. Он назван " ограничения_типизирующих_параметров
"типизирующие параметры:ограничения" ". Рассмотрим его назначение.
Во многих случаях обобщённый класс не только хранит значения, тип которых определён типизирующим параметром, но и включает средства (обычно это методы класса или его объектов) для обработки этих значений. Предположим,
что в экземплярах (в объектах) специализированных типов, порождаемых обобщённым классом Stack<ItemType>, нужно хранить только такие данные, для которых выполняется определённое условие, истинность которого устанавливается с помощью метода CompareTo(). Проверку этого условия можно выполнять в
методе Push(), определив его, например, таким образом: public void Push (ItemType data) {
if (((IComparable)data).CompareTo(default(ItemType)<0)… return;
…
}
В теле метода Push() значение параметра data приводится к значению типа

интерфейса IComparable. Только после этого к результату приведения можно будет применить метод CompareTo(), специфицированный в интерфейсе IComparable.
Этот интерфейс определён в пространстве System и предназначен для сравнения
объектов. Декларация интерфейса: intemface IComparable {
int CompareTo (object p);
}
Как видите, интерфейс содержит прототип только одного метода. Назначение
метода CompareTo() – сравнивать значение того объекта, к которому он применён
(для которого вызван этот метод), со значением объекта-аргумента. Этот аргумент
заменяет параметр p, типом которого служит класс оbject. Так как оbject – общий базовый класс для классов библиотек и программ на C#, то аргумент может иметь
любой тип. Предполагается, что целочисленный результат, возвращаемый методом
CompareTo( ), удовлетворяет следующим соглашениям.
Результат меньше нуля , если вызывающий объект нужно считать меньшим
нежели объект-параметр.
Результат больше нуля, если вызывающий объект нужно считать большим
нежели объект-параметр.
Результат равен нулю , если вызывающий объект нужно считать равным
объекту-параметру.
В методе Push() при обращении к методу CompareTo() в качестве аргумента
использовано особое выражение механизма обобщений default(ItemType).
Результат его вычисления – принятое по умолчанию значение того типа,
который будет замещать типизирующий параметр ItemType при создании специализации (инстанцировании, конкретизации) обобщённого класса Stack<>.
После такого изменения метода Push() на основе обобщённого класса
Stack<ItemType> можно создавать сконструированные типы, используя только типизирующие аргументы, классы которых реализовали интерфейс IComparable.
Предопределённые типы языка C#, такие как, например, int (System.Int32) или