c1.AboutToBlow += delegate (object sender, CarEventArgs e){
Console.WriteLine("Важное сообщение от Car: {0}", e.msg);
};
Доступ к "внешним" переменным
Анонимные методы интересны в том отношении, что они позволяют доступ к локальным переменным определяющего их метода. Формально говоря, такие переменные являются "внешними переменными" анонимного метода. Для примера предположим, что наш метод Main() определяет локальную целую переменную-счетчик с именем aboutToBlowCounter. В рамках анонимных методов, обрабатывающих событие AboutToBlow, мы будем увеличивать этот счетчик на 1 и печатать его значение в конце Main().
static void Main(string[] args) {
…
int aboutToBlowCounter = 0;
// Создание машины.
Car c1 = new Car("SlugBug", 100, 10);
// Регистрация обработчиков событий в виде анонимных методов.
c1.AboutToBlow += delegate {
aboutToBlowCounter++;
Console.WriteLine("Ox! Едем слишком быстро!");
};
c1.AboutToBlow += delegate(string msg) {
aboutToBlowCounter++;
Console.WriteLine("Важное сообщение от Car: {0}", msg);
};
…
Console.WriteLine("Событие AboutToBlow вызывалось {0} раз(а).", aboutToBlowCounter);
Console.ReadLine();
}
В результате выполнения этого обновленного метода Main() завершающий оператор Console.WriteLine() сообщит вам о том, что событие AboutToBlow генерировалось дважды.
Замечание.Анонимный метод не имеет возможности получить доступ к параметрам ref и out определяющего метода.
Групповое преобразование методов в C#
Еще одной связанной с делегатами и событиями возможностью в C# является так называемое групповое преобразование методов. Эта возможность позволяет регистрировать "просто" имя обработчика событий. Чтобы пояснить это на примере, мы снова рассмотрим тип SimpleMath, уже рассматривавшийся в этой главе выше, но добавим в него новое событие, которому будет назначено имя ComputationFinished.
public class SimpleMath {
// Здесь мы не утруждаем себя созданием
// производного типа System.EventArgs.
public delegate void MathMessage(string msg);
public event MathMessage ComputationFinished;
public int Add(int x, int y) {
ComputationFinished("Сложение выполнено.");
return х + y;
}
public int Subtract(int x , int y) {
ComputationFinished("Вычитание выполнено.");
return x – у;
}
}
Если не использовать синтаксис анонимных методов, то мы должны обработать событие ComputationComplete так, как показано ниже.
class Program {
static void Main(string[] args) {
SimpleMath m = new SimpleMath();
m.ComputationFinished += new SimpleMath.MathMessage(ComputationFinishedHandler);
Console.WriteLine("10 + 10 равно {0}", m.Add(10, 10));
Console.ReadLine();
}
static void ComputationFinishedHandler(string msg) { Console.WriteLine(msg); }
}
Но можно зарегистрировать программу обработки событий для конкретного события и так, как показано ниже (в остальном программный код изменений не претерпевает).
m.ComputationFinished += ComputationFinishedHandler;
Обратите внимание на то, что мы не создаем непосредственно соответствующий тип делегата, а просто указываем метод, который соответствует ожидаемой сигнатуре делегата (в данном случае это метод, не возвращающий ничего и получающий один объект типа System.String). Ясно, что компилятор C# при этом должен обеспечить типовую безопасность. Если метод ComputationFinishedHandler() не получает System.String и не возвращает void, то вы получите сообщение об ошибке компиляции.
Можно и явно конвертировать обработчик события в экземпляр соответствующего делегата. Это может оказаться полезным тогда, когда нужно получить соответствующий делегат, использующий заранее определенный метод. Например:
// .NET 2.0 допускает преобразование обработчиков событий
// в соответствующие делегаты.
SimpleMath.MathMessage mmDelegate = (SimpleMath.MathMessage)ComputationFinishedHandler;
Console.WriteLine(mmDelegate.Method);
Если выполнить этот программный код, то заключительный оператор Console.WriteLine() напечатает сигнатуру ComputationFinishedHandler, как показано на рис. 8.9.
Рис. 8.9. Можно извлечь делегат из соответствующего обработчика события
Исходный код. Проект AnonymousMethods размещен в подкаталоге, соответствующем главе 8.
В этой главе был рассмотрен ряд подходов, позволяющих реализовать возможность двухстороннего взаимодействия объектов. Сначала было рассмотрено использование интерфейсов обратного вызова, которые обеспечивают возможность для объекта А вызывать объект В с помощью общего интерфейса. Этот подход не является специфическим для .NET, а может использоваться в любых языках и на любых платформах, допускающих программирование на основе интерфейсов.
Читать дальше