Шаблоны слабых событий
В приложениях существует возможность того, что обработчики, присоединенные к источнику событий, не будут уничтожены в соответствии с объектом-прослушивателем, который присоединил обработчик к источнику. Такая ситуация может привести к утечке памяти. Windows Presentation Foundation (WPF) представляет шаблон, который можно использовать для решения этой проблемы путем предоставления выделенного класса диспетчера для конкретных событий и реализации интерфейса прослушивателей для данного события. Этот шаблон разработки называется шаблоном слабых событий.
Почему следует реализовать шаблон слабых событий?
Ожидание событий может привести к утечке памяти. Обычным методом для прослушивания событий является использование синтаксиса конкретного языка, который присоединяет обработчик к событию на источнике. Например, в C# такой синтаксис имеет вид: source.SomeEvent += new SomeEventHandler(MyEventHandler).
Этот метод создает строгую ссылку от источника событий к слушателю событий. Обычно присоединение обработчика событий к прослушивателю служит причиной того, что прослушиватель имеет время жизни объекта, на которое влияет время жизни объекта источника (если обработчик событий не удаляется явным образом). Однако в некоторых случаях может требоваться, чтобы время жизни объекта-прослушивателя управлялось другими факторами (например, текущей принадлежностью к визуальному дереву приложения), а не временем жизни источника. Всякий раз, когда время жизни объекта-источника превосходит время жизни объекта-слушатель, обычный шаблон события приводит к утечке памяти: слушатель хранится дольше, чем ожидается.
Шаблон слабых событий предназначен для решения проблемы утечки памяти. Шаблон слабых событий может использоваться, если прослушивателю необходимо зарегистрироваться для события, а момент отмены регистрации явно неизвестен. Шаблон слабых событий может также использоваться всякий раз, когда время жизни объекта-источника превосходит полезное время жизни объекта-прослушивателя. (В этом случае полезность определяется автором.) Шаблон слабых событий позволяет прослушивателю регистрировать и получать события, никак не затрагивая характеристики времени жизни объекта прослушивателя. В результате неявная ссылка от источника не определяет, подлежит ли прослушиватель обработке при сборке мусора. Эта ссылка является слабой, с чем и связано название шаблона слабых событий и соответствующих APIs. Слушатель может быть собран как мусор или, в противном случае, уничтожен, а источник может продолжить свое существование без сохранения не входящих в коллекцию ссылок ресурса на только что уничтоженный объект.
Кто должен реализовывать шаблон слабых событий?
Реализация шаблона слабых событий будет интересна главным образом авторам элементов управления. Как автор элемента управления, вы в значительной мере отвечаете за поведение и включения элемента управления и за то влияние, которое этот элемент оказывает на приложения, в которые он вставлен. Сюда входит и поведение времени жизни объекта элемента управления, в частности, обработка описанной проблемы утечки памяти.
Некоторые сценарии изначально подходят для применения шаблона слабых событий. Один из таких сценариев — это привязка данных. В привязке данных часто исходный объект полностью независим от объекта прослушивателя, являющегося целевых объектом привязки. Многие аспекты привязки данных WPF уже имеют шаблон слабых событий, примененный в способе реализации событий.
Как реализовать шаблон слабых событий
Реализация шаблона слабых событий состоит из трех аспектов.
Получение производного диспетчера из класса WeakEventManager.
Реализация интерфейса IWeakEventListener для любого класса, которому требуется зарегистрировать слушателей для слабого события без генерации строгой ссылки на источник.
При регистрации слушателей не следует использовать обычные методы доступа событий добавления и удаления, в которых слушатель должен использовать шаблон. Вместо этого используйте реализации AddListener и RemoveListener в выделенном диспетчере WeakEventManager для этого события.
WeakEventManager
Чтобы реализовать шаблон слабых событий, обычно создается управляющий класс со связью 1:1 с событием. Например, если имеется событие с именем Spin, то создается класс SpinEventManager, являющийся выделенным диспетчером слабых событий для данного события. Если событие существует в нескольких классах, функционирует в целом одинаковым образом в каждом классе и использует одинаковый тип данных события, то один и тот же диспетчер может использоваться для всех событий.
При создании класса, производного от класса WeakEventManager, переопределяются два виртуальных метода и предоставляется доступ к нескольким другим членам, имена которых специально не управляются виртуальным шаблоном, но тем не менее должны существовать. Переопределения используются для запуска или завершения режима доставки события инфраструктурой WPF. Другие члены обеспечивают функциональные возможности, чтобы собственные реализации IWeakEventListener могли использовать WeakEventManager для присоединения прослушивателей к событию.
Дополнительные сведения о создании классов, производных от класса WeakEventManager, см. в разделе "Примечания к наследующим методам" в разделе справки по WeakEventManager.
IWeakEventListener
Интерфейс IWeakEventListener содержит единственный метод интерфейса с именем ReceiveWeakEvent. Реализация ReceiveWeakEvent должна быть централизованной реализацией, которая направляет все ссылки событий, размещенных в этом классе, к соответствующему WeakEventManager.
Дополнительные сведения о реализации интерфейса IWeakEventListener см. в разделе "Примечания для разработчиков" в разделе справки по методу ReceiveWeakEvent.
Присоединение слушателей
Предположим, что имеется обычное событие ClockwiseSpin (определенное типом Spinner). Если имеется класс-прослушиватель SpinListener, которому требуется быть прослушивателем, обычная методика присоединения обработчика (без использования шаблона) будет использовать оператор +=:
spinnerInstance.ClockwiseSpin += new EventHandler(MyOnCWSpinHandler);
Если имеется класс, реализующий IWeakEventListener и учетные записи для события ClockwiseSpin и его диспетчера в реализации, то для шаблона слабых событий используется следующий синтаксис:
ClockwiseSpinEventManager.AddListener(spinnerInstance, this);
Логика обработки этого события задается в одном из случаев реализации ReceiveWeakEvent в классе (в отличие от обычного обработчика, основанного на делегате).
Реализация шаблона для внешних событий
Интересным аспектом шаблона слабых событий является то, что можно реализовать шаблон события, которое не является частью базы кода. С точки зрения источника, способ подключения обработчиков к событию не отличается и управляется через WeakEventManager. Требуется только определить WeakEventManager для этого события, а затем представить это событие как часть логики ReceiveWeakEvent любого предполагаемого прослушивателя, желающего использовать шаблон слабых событий для прослушивания данного события.