public static void CarAboutToBlow(string msg) {Console.WriteLine (msg);}
public static void CarIsAlmostDoomed(string msg) {Console.WriteLine("Важное
сообщение от Car: {0}", msg);}
public static void CarExploded(string msg) {Console.WriteLine(msg);}
}
В программном воде CIL операция += преобразуется в вызов статического метода Delegate.Combine (можно было бы вызвать Delegate.Combine непосредственно, но операция += предлагает более простую альтернативу). Взгляните, например, на CIL-представление метода OnAboutToBlow.
ldfld class CarDelegate.Car/AboutToBlow CarDelegate.Car::almostDeadList
ldarg.1
call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
castclass CarDelegate.Car/AboutToBlow
stfld class СarDelegate.Car/AboutToBlow CarDelegate.Car::almostDeadList
ret
}
Класс Delegate определяет также статический метод Remove, который позволит вызывающей стороне динамически удалять элементы из списка вызовов. Легко догадаться, что в C# разработчики могут для этого использовать перегруженную операцию -=. Чтобы предоставить вызывающей стороне возможность не привязываться к обозначениям AboutToBlow и Exploded, можно добавить в тип Car следующие вспомогательные методы (обратите внимание на операцию -=).
public class Car {
// Удаление элемента из списка вызовов.
public void RemoveAboutToBlow(AboutToBlow clientMethod) {almostDeadList -= clientMethod;}
public void RemoveExploded(Exploded clientMethod) {explodedList -= clientMethod;}
...
}
Здесь синтаксис -= тоже выступает в качестве простого сокращения для вызова статического метода Delegate.Remove, что доказывается следующим программным кодом CIL для члена RemoveAboutToBlow типа Car.
ldfld class CarDelegate.Car/AboutToBlow CarDelegate.Car::almostDeadList
ldarg.1
call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
castclass CarDelegate.Car/AboutToBlow
stfld class CarDelegate.Car/AboutToBlow CarDelegate.Car::almostDeadList
ret
}
Если
вызывающая сторона пожелает удалить элемент из списка вызовов делегата, мы должны предоставить тот же объект делегата, который был ранее добавлен. Поэтому мы можем прекратить выдачу сообщения Exploded, обновив Main так, как показано ниже.
static void Main(string[] args) {
Car cl = new Car("SlugBug", 100, 10);
// Сохранение объекта Car.Exploded делегата.
Car.Exploded d = new Car.Exploded(CarExploded);
cl.OnExploded(d);
…
// Удаление метода CarExploded из списка вызовов.
cl.RemoveExploded(d);
…
}
Вывод нашего приложения CarDelegate показан на рис. 8.5.
Рис. 8.5. Приложение CarDelegate за работой
Исходный код. Проект CarDelegate размещен в подкаталоге, соответствующем главе 8.
Более совершенный пример делегата
Чтобы продемонстрировать более интересные варианты использования делегатов, мы добавим в класс Car пару членов-переменных логического типа. Первый из добавляемых членов предназначен для индикации того, что автомобиль необходимо помыть (isDirty), а второй будет сообщать о том, что автомобиль требует замены шин (shouldRotate). Чтобы позволить пользователю объекта взаимодействовать с этими новыми данными состояния, для Car определяются необходимые дополнительные свойства и обновляется конструктор. Вот как выглядит соответствующим образом модифицированный программный код.
// Обновленный класс Car.
public class Car {
…
// Не пора ли помыть? Не пора ли сменить шины?
private bool isDirtу;
private bool shouldRotate;
// Дополнительные параметры для установки булевых значений.
public Car(string name, int maxSp, int currSp, bool washCar, bool rotateTires) {
…
isDirty = washCar;
shouldRotate = rotateTires;
}
public bool Dirty {
get { return isDirty; }
set { isDirty = value; }
}
public bool Rotate {
get { return shouldRotate; }
set { shouldRotate = value; }
}
}
Также предположим, что в тип Car вложен новый делегат CarDelegate.