using System;
using SimpleDelegate;
Console.WriteLine("***** Simple Delegate Example *****\n");
// Делегаты могут также указывать на методы экземпляра.
SimpleMath m = new SimpleMath();
BinaryOp b = new BinaryOp(m.Add);
// Вывести сведения об объекте.
DisplayDelegateInfo(b);
Console.WriteLine("10 + 10 is {0}", b(10, 10));
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
{
...
<b> // 1. Определить тип делегата.</b>
public delegate void CarEngineHandler(string msgForCaller);
<b> // 2. Определить переменную-член этого типа делегата.</b>
private CarEngineHandler _listOfHandlers;
<b> // 3. Добавить регистрационную функцию для вызывающего кода.</b>
public void RegisterWithCarEngine(CarEngineHandler methodToCall)
{
_listOfHandlers = methodToCall;
}
}
В приведенном примере обратите внимание на то, что типы делегатов определяются прямо внутри области действия класса
Car
; безусловно, это необязательно, но помогает закрепить идею о том, что делегат естественным образом работает с таким отдельным классом. Тип делегата
CarEngineHandler
может указывать на любой метод, который принимает значение
string
как параметр и имеет
void
в качестве возвращаемого типа.
Кроме того, была объявлена закрытая переменная-член делегата (
_listOfHandlers
) и вспомогательная функция (
RegisterWithCarEngine()
), которая позволяет вызывающему коду добавлять метод в список вызовов делегата.
На заметку! Строго говоря, переменную-член типа делегата можно было бы определить как
public
, избежав тем самым необходимости в создании дополнительных методов регистрации. Тем не менее, за счет определения этой переменной-члена типа делегата как
private
усиливается инкапсуляция и обеспечивается решение, более безопасное в отношении типов. Позже в главе при рассмотрении ключевого слова event языка C# мы еще вернемся к анализу рисков объявления переменных-членов с типами делегатов как public.
Теперь необходимо создать метод
Accelerate()
. Вспомните, что цель в том, чтобы позволить объекту
Car
отправлять связанные с двигателем сообщения любому подписавшемуся прослушивателю. Вот необходимое обновление:
<b>// 4. Реализовать метод Accelerate() для обращения к списку</b>
<b>// вызовов делегата в подходящих обстоятельствах.</b>
public void Accelerate(int delta)