Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Console.ReadLine();
В данном случае вывод будет выглядеть следующим образом:
***** Simple Delegate Example *****
Method Name: Int32 Add(Int32, Int32)
Type Name: SimpleDelegate.SimpleMath
10 + 10 is 20
Отправка уведомлений о состоянии объекта с использованием делегатов
Очевидно, что предыдущий пример SimpleDelegate был чисто иллюстративным по своей природе, т.к. нет особых причин создавать делегат просто для того, чтобы сложить два числа. Рассмотрим более реалистичный пример, в котором делегаты применяются для определения класса Car, обладающего способностью информировать внешние сущности о текущем состоянии двигателя. В таком случае нужно выполнить перечисленные ниже действия.
1. Определить новый тип делегата, который будет использоваться для отправки уведомлений вызывающему коду.
2. Объявить переменную-член этого типа делегата в классе Car.
3. Создать в классе Car вспомогательную функцию, которая позволяет вызывающему коду указывать метод для обратного вызова.
4. Реализовать метод Accelerate() для обращения к списку вызовов делегата в подходящих обстоятельствах.
Для начала создайте новый проект консольного приложения по имени CarDelegate. Определите в нем новый класс Car, начальный код которого показан ниже:
using System;
using System.Linq;
namespace CarDelegate
{
public class Car
{
// Внутренние данные состояния.
public int CurrentSpeed { get; set; }
public int MaxSpeed { get; set; } = 100;
public string PetName { get; set; }
// Исправен ли автомобиль?
private bool _carIsDead;
// Конструкторы класса.
public Car() {}
public Car(string name, int maxSp, int currSp)
{
CurrentSpeed = currSp;
MaxSpeed = maxSp;
PetName = name;
}
}
}
А теперь модифицируйте его, выполнив первые три действия из числа указанных выше:
public class Car
{
...
// 1. Определить тип делегата.
public delegate void CarEngineHandler(string msgForCaller);
// 2. Определить переменную-член этого типа делегата.
private CarEngineHandler _listOfHandlers;
// 3. Добавить регистрационную функцию для вызывающего кода.
public void RegisterWithCarEngine(CarEngineHandler methodToCall)
{
_listOfHandlers = methodToCall;
}
}
В приведенном примере обратите внимание на то, что типы делегатов определяются прямо внутри области действия класса Car; безусловно, это необязательно, но помогает закрепить идею о том, что делегат естественным образом работает с таким отдельным классом. Тип делегата CarEngineHandler может указывать на любой метод, который принимает значение string как параметр и имеет void в качестве возвращаемого типа.
Кроме того, была объявлена закрытая переменная-член делегата (_listOfHandlers) и вспомогательная функция (RegisterWithCarEngine()), которая позволяет вызывающему коду добавлять метод в список вызовов делегата.
На заметку! Строго говоря, переменную-член типа делегата можно было бы определить как public, избежав тем самым необходимости в создании дополнительных методов регистрации. Тем не менее, за счет определения этой переменной-члена типа делегата как private усиливается инкапсуляция и обеспечивается решение, более безопасное в отношении типов. Позже в главе при рассмотрении ключевого слова event языка C# мы еще вернемся к анализу рисков объявления переменных-членов с типами делегатов как public.
Теперь необходимо создать метод Accelerate(). Вспомните, что цель в том, чтобы позволить объекту Car отправлять связанные с двигателем сообщения любому подписавшемуся прослушивателю. Вот необходимое обновление:
// 4. Реализовать метод Accelerate() для обращения к списку
// вызовов делегата в подходящих обстоятельствах.
public void Accelerate(int delta)
{
/// Если этот автомобиль сломан, то отправить сообщение об этом.
if (_carIsDead)
{
_listOfHandlers?.Invoke("Sorry, this car is dead...");
}
else
{
CurrentSpeed += delta;
// Автомобиль почти сломан?
if (10 == (MaxSpeed - CurrentSpeed))
{
_listOfHandlers?.Invoke("Careful buddy! Gonna blow!");
}
if (CurrentSpeed >= MaxSpeed)
{
_carIsDead = true;
}
else
{
Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
}
}
}
Обратите внимание, что при попытке вызова методов, поддерживаемых переменной-членом _listOfHandlers, используется синтаксис распространения null. Причина в том, что создание таких объектов посредством вызова вспомогательного метода RegisterWithCarEngine() является задачей вызывающего кода. Если вызывающий код не вызывал RegisterWithCarEngine(), а мы попытаемся обратиться к списку вызовов делегата, то получим исключение NullReferenceException во время выполнения. Теперь, когда инфраструктура делегатов готова, внесите в файл Program.cs следующие изменения:
using System;
using CarDelegate;
Console.WriteLine("** Delegates as event enablers **n");
// Создать объект Car.
Car c1 = new Car("SlugBug", 100, 10);
// Сообщить объекту Car, какой метод вызывать,
// когда он пожелает отправить сообщение.
c1.RegisterWithCarEngine(
new Car.CarEngineHandler(OnCarEngineEvent));
// Увеличить скорость (это инициирует события).
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
c1.Accelerate(20);
}
Console.ReadLine();
// Цель для входящих сообщений.
static void OnCarEngineEvent(string msg)
{
Console.WriteLine("n*** Message From Car Object ***");
Console.WriteLine("=> {0}", msg);
Console.WriteLine("********************n");
}
Код начинается с создания нового объекта Car. Поскольку вас интересуют события, связанные с двигателем, следующий шаг заключается в вызове специальной регистрационной функции RegisterWithCarEngine(). Вспомните, что метод RegisterWithCarEngine() ожидает получения экземпляра вложенного делегата CarEngineHandler, и как в случае любого делегата, в параметре конструктора передается метод, на который он должен указывать. Трюк здесь в том, что интересующий метод находится в классе Program! Обратите также внимание, что метод OnCarEngineEvent() полностью соответствует связанному делегату, потому что принимает string и возвращает void. Ниже показан вывод приведенного примера:
***** Delegates as event enablers *****
***** Speeding up *****
CurrentSpeed = 30
CurrentSpeed = 50
CurrentSpeed = 70
***** Message From