In typical applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. This situation can lead to memory leaks. Windows Presentation Foundation (WPF) introduces a particular design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. This design pattern is known as the WeakEvent pattern.
Listening for events can lead to memory leaks. The typical technique for listening to an event is to use the language-specific syntax that attaches a handler to an event on a source. For instance, in C#, that syntax is: source.SomeEvent += new SomeEventHandler(MyEventHandler).
This technique creates a strong reference from the event source to the event listener. Ordinarily, attaching an event handler for a listener causes the listener to have an object lifetime that is influenced by the object lifetime for the source (unless the event handler is explicitly removed). But in certain circumstances you might want the object lifetime of the listener to be controlled only by other factors, such as whether it currently belongs to the visual tree of the application, and not by the lifetime of the source. Whenever the source object lifetime extends beyond the object lifetime of the listener, the normal event pattern leads to a memory leak: the listener is kept alive longer than intended.
The WeakEvent pattern is designed to solve this memory leak problem. The WeakEvent pattern can be used whenever a listener needs to register for an event but the listener does not explicitly know when to unregister, and whenever the object lifetime of the source exceeds the "useful" object lifetime of the listener. (The concept of "useful" is up to you to define.) The WeakEvent pattern allows the listener to register for and receive the event without affecting the object lifetime characteristics of the listener in any way. In effect, the implied reference from the source does not count when deciding whether the listener is eligible for garbage collection. The reference is a weak reference, thus the naming of the WeakEvent pattern and the related APIs. The listener can be garbage collected or otherwise destroyed, and the source can continue without retaining noncollectible handler references to a now destroyed object.
Implementing the WeakEvent pattern will be interesting primarily for control authors. This is because as a control author you are largely responsible for the behavior and containment of your control and the impact it has on applications in which it is inserted. This includes the control object lifetime behavior, in particular the handling of the memory leak problem described.
Certain scenarios inherently lend themselves to application of the WeakEvent pattern. One such scenario is data binding, where it is often the case that a source object that is a data source is completely independent of a listener object, which is a target of a binding. Many aspects of WPF data binding already have the WeakEvent pattern applied in how the events are implemented.
Implementing the WeakEvent pattern consists of three aspects:
Derive a manager from the WeakEventManager class.
Implement the IWeakEventListener interface on any class that wants to register listeners for the weak event without generating a strong reference to the source.
When registering listeners, do not use the conventional add and remove accessors of the event where you want the listener to use the pattern. Instead, use the "AddListener" and "RemoveListener" implementations in the dedicated WeakEventManager for that event.
Typically, you will create manager classes at a 1:1 relationship to events that implement the pattern. For instance, if you have an event Spin, you would derive a SpinEventManager class as the dedicated weak event manager for the event. If the event existed in more than one source class, and behaved generally the same in each class and shared the event data type, the same manager could be used for each.
The implementation checklist for deriving from the WeakEventManager class consists of overriding two virtual methods, and exposing several other members whose names are not specifically governed by a virtual template, but should exist nonetheless. The overrides are used to initiate or terminate event delivery mode by the WPF infrastructure. The other members are necessary to provide functionality so that your own IWeakEventListener implementations can use the WeakEventManager to attach listeners to the event.
An IWeakEventListener implementing class has just one responsibility: implementing the interface method ReceiveWeakEvent. The ReceiveWeakEvent implementation must be a centralized implementation that directs any event reference that exists on that class to the appropriate WeakEventManager.
Suppose you had a ClockwiseSpin event (defined by Spinner) that is a conventional event. To use the pattern for this event, you would either use an existing ClockwiseSpinEventManager class derived from WeakEventManager, or implement it yourself. If you have a SpinListener listener class that wants to be a listener, the conventional technique (not using the pattern) for attaching the handler would to use the += syntax:
spinnerInstance.ClockwiseSpin += new EventHandler(MyOnCWSpinHandler);
But if you have a class that implements IWeakEventListener and accounts for the ClockwiseSpin event and its manager in the implementation, the syntax to use instead for the WeakEvent pattern is:
Then, your handling logic for that event is specified within one of the cases of the ReceiveWeakEvent implementation on your class, not as a conventional delegate-based handler.
Implementing the Pattern for External Events
One interesting aspect of the WeakEvent pattern is that you can implement the pattern against an event that is not part of your code base. From the perspective of the source, the way that handlers are attached to its event does not differ, and is controlled by the WeakEventManager. You only need to define a WeakEventManager for that event, and then account for that event as part of the ReceiveWeakEvent logic on any prospective listener that wants to use the pattern to listen to that event.