До этого момента в нашей книге в каждом примере приложении программный код Main тем или иным способом направлял запросы соответствующим объектам. Но мы пока что не рассматривали возможность обратного обращения объекта к вызывающей стороне. В большинстве программ типичным для объектов системы оказывается двухсторонняя коммуникация, которая реализуется с помощью интерфейсов обратного вызова, событий и других программных конструкций. Эта глава начинается с рассмотрения того, как с помощью типов интерфейса реализовать функциональные возможности обратного вызова.
Затем вы узнаете о типе делегата .NET, который является объектом, обеспечивающим типовую безопасность и "указывающим" на метод или методы, которые могут быть вызваны позднее. Но, в отличие от традиционного указателя на функцию в C++, делегаты .NET представляют собой объекты, которые имеют встроенную поддержку многоадресного и асинхронного вызова методов. Мы рассмотрим асинхронное поведение типов делегата позже, при изучении пространства имен System.Threading (см. главу 14).
Выяснив, как создавать типы делегата и работать с ними, мы с вами рассмотрим ключевое слово C# event, которое призвано упростить и ускорить работу с типами делегата. Наконец, в этой главе будут рассмотрены новые возможности языка C#
Интерфейсы обратного вызова
При рассмотрении материала предыдущей главы вы имели возможность убедиться, что интерфейсы определяют доведение, которое может поддерживаться самыми разными типами. Кроме использований интерфейсов для поддержки полиморфизма, их можно использовать и в качестве
Чтобы показать варианты использования интерфейсов обратного вызова, мы изменим уже знакомый нам тип Car так, чтобы он мог информировать вызывающую сторону о приближении поломки машины (т.е. о том, что текущая скорость на 10 км/ч ниже максимальной скорости) и о свершившейся поломке (когда текущая скорость равна или выше максимальной скорости). Способность посылать и принимать соответствующие события будет реализована с помощью интерфейса, носящего имя IEngineEvents.
// Интерфейс обратного вызова.
public interface IEngineEvents {
void AboutToBlow(string msg);
void Exploded (string msg);
}
Интерфейсы событий обычно реализуются не объектом, непосредственно "заинтересованным" в получении событий, а некоторым вспомогательным объектом, который называется
// Приемник событий Car.
public class CarEventSink: IEngineEvents {
private string name;
public CarEventSink{}
public CarEventSink(string sinkName) { name = sinkName; }
public void AboutToBlow(string msg) { Console.WriteLine("{0} сообщает: {1}", name, msg); }
public void Exploded(string msg) { Console.WriteLine(" {0} сообщает: {1}", name, msg); }
}
Теперь, когда у нас есть объект-приемник, реализующий интерфейс событий, нашей следующей задачей является передача ссылки на этот приемник в тип Car. Тип Car будет хранить эту ссылку и при необходимости выполнять обратные вызовы приемника. Чтобы тип Car мог получить ссылку на приемник, нужно добавить в тип Car вспомогательный член, который мы назовем Advise. Точно так же. если вызывающая сторона пожелает отменить привязку к источнику событий, она может вызвать другой вспомогательный метод типа Car – метод Unadvise. Наконец, чтобы позволить вызывающей стороне регистрировать множество приемников событий (с целью групповой адресации), тип Car поддерживает ArrayList для представления исходящих соединений.
// Тип Car и вызывающая сторона могут связываться
// с помощью интерфейса IEngineEvents.
public class Car {
// Набор связанных приемников.
ArrayList clientSinks = new ArrayList;