// Добавление элементов в список вызовов
// с помощью вспомогательных методов.
public void OnAboutToBlow(AboutToBlow clientMethod) {almostDeadList = clientMethod;}
public void OnExploded(Exploded clientMethod) {explodedList = clientMethod;}
…
}
Обратите внимание на то, что в этом примере мы определяем типы делегата непосредственно в рамках типа Car. Если исследовать библиотеки базовых классов, то станет ясно, что определение делегата в рамках типа, с которым он обычно работает, является вполне типичным. В связи с этим, поскольку компилятор преобразует делегат в полное определение класса, мы здесь фактически создаем вложенные классы.
Далее обратите внимание на то, что здесь объявлены члены-переменные (по одному для каждого типа делегата) и вспомогательные функции (OnAboutToBlow и OnExploded), которые позволят клиенту добавлять методы в списки вызовов делегатов. В принципе эти методы подобны методам Advise и Unadvise, которые были нами созданы в примере с EventInterfасе. Но в данном случае входящим параметром оказывается размещаемый клиентом объект делегата, а не класс, реализующий конкретный интерфейс.
Здесь мы должны обновить метод Accelerate, чтобы вызывались делегаты, а не просматривались объекты ArrayList приемников клиента (как это было в примере с EventInterfасе). Подходящая модификация может выглядеть так.
public void Accelerate(int delta) {
// Если машина 'сломалась', генерируется событие Exploded.
if (carIsDead) {
if (explodedList != null) explodedList("Извините, машина сломалась…");
} elsе {
currSpeed += delta;
// Вот-вот сломается?
if (10 == maxSpeed – currSpeed && almostDeadList != null) {
almostDeadList("Осторожно! Могу сломаться!");
}
// Пока все OK!
if (currSpeed ›= maxSpeed) carIsDead = true;
else Console.WriteLine("CurrSpeed = {0}", currSpeed);
}
}
Обратите внимание на то, что перед вызовом методов, связанных с членами-переменными almostDeadList и explodedList, их значения проверяются на допустимость. Причина в том, что размещение соответствующих объектов с помощью вызова вспомогательных методов OnAboutToBlow и OnExploded будет задачей вызывающей стороны. Если вызывающая сторона не вызовет эти методы, а мы попытаемся получить список вызовов делегата, то будет сгенерировано исключение NullReferenseException и в среде выполнения возникнут проблемы (что, конечно же, нежелательно).
Теперь, когда инфраструктура делегата имеет нужный нам вид, рассмотрим модификацию класса Program.
class Program {
static void Main(string[] args) {
Console.WriteLine("***** Делегаты и контроль событий *****");
// Обычное создание класса Car
Car cl = new Car("SlugBug", 100, 10);
// Регистрация обработчиков событий для типа Car.
cl.OnAboutToBlow(new Car.AboutToBlow(CarAboutToBlow));
cl.OnExploded(new Car.Exploded(CarExploded));
// Ускоряемся (при этом генерируются события) .
Console.WriteLine("\n***** Ускорение *****");
for(int i = 0; i ‹ 6; i++) cl.Accelerate(20);
Console.ReadLine;
}
// Car будет вызывать эти методы.
public static void CarAboutToBlow(string msg) {Console.WriteLine(msg);}
public static void CarExploded(string msg) {Console.WriteLine(msg);}
}
Здесь следует отметить только то, вызывающая сторона задает значения членам-переменным делегата с помощью вспомогательных методов регистрации. Кроме того, поскольку делегаты AboutToBlow и Exploded вложены в класс Car, при их размещении следует использовать полные имена (например, Car.AboutToBlow). Как любому конструктору, мы передаем конструктору делегата имя метода, который нужно добавить в список вызовов. В данном случае это два статических члена класса Program (если вложить указанные методы в новый класс, это будет очень похоже на тип CarEventSink из примера Event Interface).