Weak Event Handlers

In a few editions of my book, I showed how to implement weak event handlers which allow an object to be GCd if no other reference is keeping the object alive. If the object is alive, it recieves the event notification and if it isn’t alive, it doesn’t receive the event notification. It has come to my attention that the code I show in my book is incorrect and does not work as advertised. I have corrected that code and the working version is shown below:

public static class WeakEventHandler {
private class WeakEventHandlerImpl {
protected readonly WeakReference m_wrTarget; // WeakReference to original delegate’s target object
protected Delegate m_openEventHandler;       // “Open” delegate to invoke original target’s delegate method

public WeakEventHandlerImpl(Delegate d) { m_wrTarget = new WeakReference(d.Target); }

// Match is used to compare a WeakEventHandlerImpl object with an actual delegate.
// Typically used to remove a WeakEventHandlerImpl from an event collection.
public Boolean Match(Delegate strongEventHandler) {
// Returns true if original target & method match the WeakEventHandlerImpl’s Target & method
return (m_wrTarget.Target == strongEventHandler.Target) && (m_openEventHandler.Method == strongEventHandler.Method);
}
}

// “Open” delegate definition to quickly invoke original delegate’s callback
private delegate void OpenEventHandler<TTarget, TEventArgs>(TTarget target, Object sender, TEventArgs eventArgs)
where TTarget : class
where TEventArgs : EventArgs;

// A proxy object that knows how to invoke a callback on an object if it hasn’t been GC’d
private sealed class WeakEventHandlerImpl<TEventHandler> : WeakEventHandlerImpl where TEventHandler : class {
// Refers to a method that removes a delegate to this proxy object once we know the original target has been GC’d
private readonly Action<TEventHandler> m_cleanup;

// This is the delegate passed to m_cleanup that needs to be removed from an event
private readonly TEventHandler m_proxyHandler;

public static TEventHandler Create(TEventHandler eh, Action<TEventHandler> cleanup) {
Contract.Requires(eh != null && cleanup != null);
// We don’t create weak events for static methods since types don’t get GC’d
Delegate d = (Delegate)(Object)eh;  // We know that all event handlers are derived from Delegate
if (d.Target == null) return eh;

var weh = new WeakEventHandlerImpl<TEventHandler>(d, cleanup);
return weh.m_proxyHandler; // Return the delegate to add to the event
}

&#1

60;  private WeakEventHandlerImpl(Delegate d, Action<TEventHandler> cleanup) : base(d) {
m_cleanup = cleanup;

Type targetType = d.Target.GetType();
Type eventHandlerType = typeof(TEventHandler);
Type eventArgsType = eventHandlerType.IsGenericType
? eventHandlerType.GetGenericArguments()[0]
: eventHandlerType.GetMethod(“Invoke”).GetParameters()[1].ParameterType;

// Create a delegate to the ProxyInvoke method; this delegate is registered with the event
var miProxy = typeof(WeakEventHandlerImpl<TEventHandler>)
.GetMethod(“ProxyInvoke”, BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(targetType, eventArgsType);
m_proxyHandler = (TEventHandler)(Object)Delegate.CreateDelegate(eventHandlerType, this, miProxy);

// Create an “open” delegate to the original delegate’s method; ProxyInvoke calls this
Type openEventHandlerType = typeof(OpenEventHandler<,>).MakeGenericType(d.Target.GetType(), eventArgsType);
m_openEventHandler = Delegate.CreateDelegate(openEventHandlerType, null, d.Method);
}

private void ProxyInvoke<TTarget, TEventArgs>(Object sender, TEventArgs e)
where TTarget : class
where TEventArgs : EventArgs {
// If the original target object still exists, call it; else call m_cleanup to unregister our delegate with the event
TTarget target = (TTarget)m_wrTarget.Target;
if (target != null)
((OpenEventHandler<TTarget, TEventArgs>)m_openEventHandler)(target, sender, e);
else m_cleanup(m_proxyHandler);
}
}

// We offer this overload because it is so common
public static EventHandler Wrap(EventHandler eh, Action<EventHandler> cleanup) {
return WeakEventHandlerImpl<EventHandler>.Create(eh, cleanup);
}
public static TEventHandler Wrap<TEventHandler>(TEventHandler eh, Action<TEventHandler> cleanup) where TEventHandler : class {
return WeakEventHandlerImpl<TEventHandler>.Create(eh, cleanup);
}
public static EventHandler<TEventArgs> Wrap<TEventArgs>(EventHandler<TEventArgs> eh, Action<EventHandler<TEventArgs>> cleanup) where TEventArgs : EventArgs {
return WeakEventHandlerImpl<EventHandler<TEventArgs>>.Create(eh, cleanup);
}
public static Boolean Match(Delegate weakEventHandler, Delegate strongEventHandler) {
return ((WeakEventHandlerImpl) weakEventHandler.

Target).Match(strongEventHandler);
}
}

To use it, instead of registerring an event callback like this:
someButton.Click += o.ClickHandler;

Do this:
someButton.Click += WeakEventHandler.Wrap(o.ClickHandler, eh => someButton.Click -= eh);

Jeffrey Richter

View Comments

  • 1) Thanks, I saw similar solutions around in the web, like here: http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx . Your example is more flexible for sure, as it allows for other EventHandler delegate types too, but do you see any other problems with it (maybe similar to the version in your book)?
    2) Your example also uses an "cleanup" action. I always wonder, people go great lengths wrapping the event handling delegate's Target into a WeakReference, but what about the Target in the "Action{TEventHandler} cleanup"? It will mostly, if not always point to the same listener object, which will be kept as a strong reference as the cleanup delegate will be stored as a field in WeakEventHandlerImpl. Where am I wrong in my understanding?
    3) Unfortunately, all of the WeakEventHandler examples I found, including this one, will throw a nasty "MethodAccessException" in Silverlight if the event handling method is not public. Mostly those methods will and should be protected or private. Do you see any workaround? I gave up on it already actually. :-(

  • .NET Time Period Library for .NET .Net Perf - timing profiler for .Net Weak Event Handlers Other Don

  • Thanks for posting the correction.
    Am I right in assuming that this replaces all the code in pages 562 to 564 of "CLR via C#, Third Edition"?

  • How can I make sure that a weak event handler is registered only once?
    One possible approach with strong event handlers is to remove it first, like this:
    someButton.Click -= o.ClickHandler;
    someButton.Click += o.ClickHandler;
    Does this approach work with weak event handlers as well? And should it be:
    someButton.Click -= WeakEventHandler.Wrap(o.ClickHandler, eh => someButton.Click -= eh);
    someButton.Click += WeakEventHandler.Wrap(o.ClickHandler, eh => someButton.Click -= eh);
    Or rather:
    someButton.Click -= o.ClickHandler;
    someButton.Click += WeakEventHandler.Wrap(o.ClickHandler, eh => someButton.Click -= eh);

Recent Posts

How to Navigate Azure Governance

 Cloud management is difficult to do manually, especially if you work with multiple cloud…

5 days ago

Why Azure’s Scalability is Your Key to Business Growth & Efficiency

Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…

3 weeks ago

Unlocking the Power of AI in your Software Development Life Cycle (SDLC)

https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…

1 month ago

The Role of FinOps in Accelerating Business Innovation

FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…

1 month ago

Azure Kubernetes Security Best Practices

Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…

1 month ago

Mastering Compliance: The Definitive Guide to Managed Compliance Services

In the intricate landscape of modern business, compliance is both a cornerstone of operational integrity…

2 months ago