Building a Leak-Proof Eventing Model
One of the main features of the .NET Framework is to provide automatic memory management via garbage collection. You might think this would solve the problem of memory leaks in managed applications, but the effectiveness of the garbage collector can be hampered by your code. The GC will not release memory from objects that have root references, which can be local or static variables. If you maintain these references, the GC will not collect them. If you create more instances and keep references to them, memory consumption will continue to grow, resulting in a leaky application.
Note: You can download the code for this blog post here.
Sometimes itâs not so apparent exactly where youâre holding a reference. This is the case with delegates, which are basically type-safe function pointers. If you fire up Reflector and take a look at a delegate definition, such as Action or EventHandler, youâll see that the constructor takes two parameters. The second is a native integer that basically points to the memory address of the target method. But the first parameter is a System.Object pointing to the instance that owns the target method. (Itâs set to null when pointing to a static method.)
public class System.EventHandler : Delegate
{
public EventHandler(object @object, IntPtr method) { } }In other words, a reference to the delegate is actually a reference to the instance where the target method is defined. If an event publisher holds the delegate reference, then you canât simply set the subscriber instance to null and expect it to be garbage collected. The best thing would be for the subscriber to unregister from the event before it gets disposed. But that means having to implement IDisposable or, worse yet, implementing a finalizer. This is error-prone and far from fool-proof. Trust me, you donât want to go there.
What we need is a way for a publisher to expose an event while allowing subscribers to be garbage collected. Enter the WeakReference, which is a reference that wonât prevent the referenced object from being garbage collected. A publisher can weakly reference an event subscriber and only fire the event if the subscriber has not been garbage collected, which it can verify by checking the IsAlive property of the weak reference.
The question is, how do you get a reference to the subscriberâs delegate without referencing the delegate? One idea is to have a weak reference to the delegate itself. But this wonât work because thereâs nothing to prevent the delegate from being garbage collected, even if the subscriber it points to is still alive.
If the publisher weakly references the delegate and the delegate gets garbage collected, thereâs no way for the publisher to fire the event. If the publisher weakly references the subscriber, but maintains a hard reference to the delegate, the subscriber wonât be garbage collected when it needs to be, resulting in a potential memory leak.
One solution is for the publisher to weakly reference the subscriber and re-create the delegate when it needs to fire the event. That way, the subscriber can be collected, because the publisher does not reference the delegate, but it can fire the target method by getting a pointer to it only if the subscriber is still alive.
Here is what that code would look like:
// Note: Donât use this code with Silverlight
class WeakDelegate
{
private readonly WeakReference subscriber;
private readonly string methodName;
private readonly Type delegateType;
public WeakDelegate(Delegate targetMethod)
{
this.subscriber = new WeakReference(targetMethod.Target);
this.methodName = targetMethod.Method.Name;
this.delegateType = targetMethod.GetType();
}
public bool IsAlive
{
get { return subscriber != null && subscriber.IsAlive; }
}
public Delegate GetDelegate()
{
if (IsAlive)
{
return Delegate.CreateDelegate(delegateType,
subscriber.Target, methodName);
}
return null;
}
}In full .NET (and consequently WPF) this code works like a charm. However, if you were to try it on Silverlight or Windows Phone, youâll soon be banging your head against a wall (at least figuratively). Those flavors of .NET put some restrictions on Delegate.CreateDelegate. First of all, the target method needs to be public or youâll get a MethodAccessException. It has to be of form Action or Action<T> or youâll get a MissingMethodException. It has to be an instance method or youâll get either a MissingMethodException or an ArgumentException. Yikes!
While it may seem like weâre between a rock and a hard place, there is a solution. The key is for the publisher to use a proxy to fire events on its behalf. The proxy, which has delegates referencing the subscriber, is referenced weakly by the publisher and strongly by the subscriber. The subscriber can be garbage collected because the delegate pointing to it is only referenced by the subscriber itself. Because the subscriber has a reference to the publisher proxy, the delegate wonât be garbage collected until the subscriber is. Best of all, this approach works across the board with full .NET / WPF, Silverlight and Windows Phone. Yay!
So what does the code look like for this pattern? First, we will need an interface that contains events that the subscriber can subscribe to. Weâll call it IPublisher.
interface IPublisher
{
event EventHandler SomeEvent;
}Notice weâre exposing an event of type EventHandler. This is just an example. You can expose any number of events that suit your fancy, including various forms of EventHandler<T>. For illustration purposes Iâm trying to keep it simple. The subscriber accepts an IPublisher to its constructor so that it can subscribe to the event as it normally would.
class Subscriber
{
IPublisher publisher;
public Subscriber(IPublisher publisher)
{
this.publisher = publisher;
publisher.SomeEvent += OnSomeEvent;
}
private void OnSomeEvent(object sender, EventArgs e)
{
Console.WriteLine('SomeEvent was fired');
}
}Thereâs nothing unusual here. Now for the publisher proxy. For that weâll need another interface: IPublisherProxy, which extends IPublisher by adding a method that fires the event. If you added more events to IPublisher, you would add corresponding methods to IPublisherProxy to fire those events.
interface IPublisherProxy : IPublisher
{
void FireEvent(object sender, EventArgs e);
}The subscriber needs to expose an IPublisherProxy to the publisher, so that it can weakly reference it and also call FireEvent. For that we need yet another interface that has a property of type IPublisherProxy. Weâll call it ISubscriber.
interface ISubscriber
{
IPublisherProxy PublisherProxy { get; }
}The subscriber will need to implement this interface and maintain a hard reference to the publisher proxy so that it will only be garbage collected when the subscriber is GCâd. Implementing it explicitly will be cleaner because only the publisher is interested in it.
class LeakProofSubscriber : ISubscriber
{
IPublisher publisher = null;
public LeakProofSubscriber(IPublisher publisher)
{
// Subscribe to events
this.publisher = publisher;
this.publisher.SomeEvent += OnSomeEvent;
}
// Explicitly implement IPublisherProxy IPublisherProxy publisherProxy = new PublisherProxy(); IPublisherProxy ISubscriber.PublisherProxy { get { return publisherProxy; } } private void OnSomeEvent(object sender, EventArgs e)
{
Console.WriteLine('SomeEvent was fired');
}
}Now the fun begins: building a leak-proof publisher. First weâll need a List<WeakReference> with weakly referenced publisher proxies. Then, in order to add proxies, we implement the event manually by providing our own add and remove constructs. The compiler simply translates these to Add and Remove methods that accept a delegate. Weâll pull out the subscriber from the Target property of the delegate, cast it to ISubscriber, read the PublisherProxy property, then add or remove the incoming delegate to its event. Lastly, weâll add or remove the proxy from our list of weak references. When we want to fire the event, we simply check to see if the proxy is alive and then call FireEvent.
class LeakProofPublisher : IPublisher
{
// List of weakly referenced proxies
private List<WeakReference> weakProxies
= new List<WeakReference>();
// Subscribers to add and remove themselves from subscriptions
public event EventHandler SomeEvent
{
add
{
// Get subscriber
ISubscriber subscriber = value.Target as ISubscriber;
if (subscriber != null)
{
// Add handler to proxy
IPublisherProxy proxy = subscriber.PublisherProxy;
proxy.SomeEvent += value;
// Add to list of weakly referenced proxies
weakProxies.Add(new WeakReference(proxy));
}
}
remove
{
// Get subscriber
ISubscriber subscriber = value.Target as ISubscriber;
if (subscriber != null)
{
// Remove handler from proxy
IPublisherProxy proxy = subscriber.PublisherProxy;
proxy.SomeEvent -= value;
// Remove from list of weakly referenced proxies
for (int i = weakProxies.Count - 1; i >= 0; i--)
{
var weakProxy = weakProxies[i];
var existing = GetProxy(weakProxy);
if (existing != null &&
object.ReferenceEquals(existing, proxy))
weakProxies.Remove(weakProxy);
}
}
}
}
// Ask proxy to fire event
public void FireEvent()
{
foreach (var weakProxy in weakProxies)
{
var proxy = GetProxy(weakProxy);
if (proxy != null)
{
proxy.FireEvent(this, EventArgs.Empty);
}
}
}
private IPublisherProxy GetProxy(WeakReference weakProxy)
{
if (weakProxy != null && weakProxy.IsAlive)
{
IPublisherProxy proxy = weakProxy.Target
as IPublisherProxy;
if (proxy != null)
{
return proxy;
}
}
return null;
}
}There you go! I didnât say the solution would be easy, but the problem is solvable. The nice part is that, while the publisher does the heavy lifting, the only extra code that the subscriber needs is the PublisherProxy property. However, because we implement it explicitly, it doesnât pollute the subscriberâs appearance. You can download the full source code here.
Although I wouldnât recommend making all your events leak-proof in this manner, there are scenarios where making events leak-proof is an essential requirement. An example would be a message bus (also called an event aggregator or event mediator), which Iâve incorporated into my Simple MVVM Toolkit (version 2 will support leak-proof events and drop the requirement to unregister for messages). You wouldnât want subscribers to not get garbage collected simply because they registered to received messages and failed to unregister.
As you can see, weak events can be a thorny problem to solve. Hopefully this approach will help if you have the need for them. Happy coding!
Reference: Building a Leak-Proof Eventing Model from our NGC partner Tony Sneed at the Tony Sneed’s Blog blog.





