Tuesday, March 20, 2012

Weak Events in .Net, the easy way

I’ve written before about one kind of memory leak the .Net Garbage Collector cannot protect against: those caused by event handlers keeping objects alive beyond their best-before date. Daniel Grunwald has a very comprehensive article on CodeProject explaining the problem in depth, and giving a number of solutions, some of which I’ve used in the past.

Nowadays, my preferred solution is one made possible by the fabulous Reactive Extensions (Rx) framework.

Suppose, as one example, you want to subscribe to a CollectionChanged event, but don’t want your object to be kept alive by that subscription. You can just do:

collection.ObserveCollectionChanged().SubscribeWeakly(this, (target, eventArgs) => target.HandleEvent(eventArgs));
 
private void HandleEvent(NotifyCollectionChangedEventArgs item)
{
    Console.WriteLine("Event received by Weak subscription");
}

How it works

Like all remedies for un-dying object problems, the active ingredient in this one is the WeakReference class. It works like this

public static class IObservableExtensions
{
    public static IDisposable SubscribeWeakly<T, TTarget>(this IObservable<T> observable, TTarget target, Action<TTarget, T> onNext) where TTarget : class
    {
        var reference = new WeakReference(target);
 
        if (onNext.Target != null)
        {
            throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
        }
 
        IDisposable subscription = null;
        subscription = observable.Subscribe(item =>
                                                    {
                                                        var currentTarget = reference.Target as TTarget;
                                                        if (currentTarget != null)
                                                        {
                                                            onNext(currentTarget, item);
                                                        }
                                                        else
                                                        {
                                                            subscription.Dispose();
                                                        }
                                                    });
 
        return subscription;
    }
}

You can see that we hold the intended recipient of the notifications, target, as a WeakReference, so that if the Garbage Collector wants to sweep it up, this subscription won’t stand in its way. Then we subscribe a lambda function of our own to the observable. When we receive a notification from the observable, we check that target is still alive, and then pass along the message. If we discover that target has died, we mourn briefly, then cancel the subscription.

Notice though, that all our clever use of WeakReferences could be subverted if the onNext delegate refers to an instance method on the target. That delegate would then be smuggling in the implicit this pointer as a strong reference to the target. onNext is itself held strongly by the closure that is created for the lambda function, so the net effect would be that the target is kept alive by the subscription.

QR: Inline image 1

Posted via email from Jasper-Net