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);

Always Here For You – 24x7x365

Atmosera has hired and developed an industry-leading team of skilled engineers, architects, technicians, systems specialists and project managers.
We offer a wealth of real world experience and proven best practices.
We are also committed to staying abreast of the latest technology vendors and offerings.

System Status and Outages

Date

Service Impacted

Comments

All Green

All Systems and Services are Fully Operational

Stay Connnected

Maintenance notices, newsletter, news flash, events, and email preferences.

SIGN UP TODAY

Access Web Help Desk

Web Help Desk, or WHD for short, is a web-based application built on the SolarWinds Web Help Desk software. If you are an authorized WHD user, use your credentials to login.

Login to WHD View WHD FAQ

Get Support

Atmosera’s Technical Assistance Command Center (TACC) monitors around the clock all aspects of your fully managed solutions from end-to-end networks to supporting services.

Open a Support Case

Contact Us Anytime

Portland, OR: 1.503.222.2202

Toll-Free: 1.800.903.4852

Fax: 1.503.646.1400

Email Sales Email Support

Legacy Services Support Sites

EasyStreet Infinity Internet

We deliver solutions that accelerate the value of Azure.

Ready to experience the full power of Microsoft Azure?

Start Today

Blog Home

Stay Connected

Upcoming Events

All Events