Dealing with memory leak is not as far as some might think it is. But it doesn’t get serious until it is very late to change the fundamentals of our software design.
In my recent project it happened to be caused by traditional event subscription pattern. Which is most likely not leaving any memory leak behind, but it could easily do so 🙁
I also looked into latest libraries of Microsoft and saw they are using a new way of subscribing event handler to GUI events in WPF. Further I saw that every Window, like any Visual object in WPF, is derrived from
DispatcherObject and is also implementing
IWeakEventListener; hummmmmm!
You can instanciate several instances of different event listeners and let the manager route the events to all of them. As long as they all implement IWeakEventListener.ReceiveWeakEvent
To demonstrate this I started a sample code about human body. Assume you have a heart class and a hand class. If you have an instance of them and you need to get events on your hand when the heart beats you might do something like:
hear.BeatEvent += right.BeatHandler
The idea is to change this aby using an event manager, in my sample I have called it Brain, to handle de notifications like
Brain.AddListener(heart, right);
And to unsubscribe to the event you do something like
Brain.RemoveListener(heart, right);
To make this possible, you need to make sure that your Hand class where the events need to be handled, you implement the IWeakEventListener interface which brings us to ReceiveWeakEvent method:
/// <summary> Receives events from the centralized event manager. </summary>
/// <returns> true if the listener handled the event. </returns>
/// <param name="managerType">The type of the <see cref="T:System.Windows.WeakEventManager"/> calling this method.</param>
/// <param name="sender">Object that originated the event.</param>
/// <param name="e">Event data.</param>
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(Brain))
{
var beat = e as BeatArgs;
Trace.WriteLine(string.Format("{0} hand is getting the beat from {1}", name, beat.TimeOfBeat.ToString("hh:mm:ss")));
return true;
}
return false;
}
The Brain class which has a Singleton implementation provides only two public methods i.e. AddListener
and RemoveListener
. They do this by calling the protected methods and passing the parameters:
/// <summary>Subscribe a body part to listen to heart beat </summary>
/// <param name="source">the heart to listen to</param>
/// <param name="listener">the body that subscribes</param>
public static void AddListener(Heart source, IWeakEventListener listener)
{
Neuron.ProtectedAddListener(source, listener);
}
/// <summary>Unsubscribe a body part to listen to heart beat </summary>
/// <param name="source">the heart to listen to</param>
/// <param name="listener">the body that subscribes</param>
public static void RemoveListener(Heart source, IWeakEventListener listener)
{
Neuron.ProtectedRemoveListener(source, listener);
}
That is not all what brain do internally. It is actually inheriting the abstract class WeakEventManager
and in order to do that it overrides the StartListening
and StopListening
methods that are internally called when it should be.
/// <summary>Starts listening on the provided heart for the managed beat event.</summary>
/// <param name="source">The heart to disconnect beat event</param>
protected override void StartListening(object source)
{
var heart = source as Heart;
if (heart == null) throw new ArgumentException("The brain suppose to listen to heart only");
heart.BeatEventHandler += DeliverEvent;
}
/// <summary>Stops listening on the provided heart for the managed beat event.</summary>
/// <param name="source">The heart to disconnect beat event</param>
protected override void StopListening(object source)
{
var heart = source as Heart;
if (heart == null) throw new ArgumentException("The brain suppose to listen to heart only");
heart.BeatEventHandler -= DeliverEvent;
}
The last part is the Heart where the event is getting fired. That is nothing special and can be implemented as before.
/// <summary> Can beat and can e subscribed to the beat event handler </summary>
public partial class Heart
{
/// <summary>Hanldler for notifying a beat </summary>
public event EventHandler<BeatArgs> BeatEventHandler;
/// <summary>Sends the beat through the events </summary>
/// <param name="beatArgs"></param>
protected void OnSendBeat(BeatArgs beatArgs)
{
if (BeatEventHandler != null)
BeatEventHandler(this, beatArgs);
}
/// <summary>Create a beat and send it </summary>
private void BeatOnce()
{
OnSendBeat(new BeatArgs());
}
}