Why would you need to use a Weak Event handler? Take a look at this example of normal events and handlers:
Notice how the Console window is empty. The Subscriber finalizer ~Subscriber() which writes to the console window has not been called meaning our subscriber is still in memory. This is due to the Publisher event holding a strong reference to the Subscribers event handler (see the watch window).
Why not just implement IDispose and unsubscribe from the event?
This is the ideal scenario but you are relying upon our Publisher being considerate enough to call dispose on the Subscriber. This means we can remove the event handler, notice how the publishers event is now null, which means the GarbageCollector can collect our subscriber.
But what happens if the Publisher forgets to “dispose” of the subscriber? We still have a memory leak.
Weak Reference Events to the rescue!
We can see here in the watch window that the publishers event still has a reference to the subscribers event handler. But the subscriber has been disposed…. cool!
This is all possible that to the use of a WeakReference.
Special thanks to Distin Campbell, Joe Duffy, Xavier Musy and Gregor R. Peisker.
using System;
using System.Reflection;
public delegate void UnregisterCallback<E>(EventHandler<E> eventHandler)
where E : EventArgs;
public interface IWeakEventHandler<E>
where E : EventArgs
{
EventHandler<E> Handler { get; }
}
public class WeakEventHandler<T, E> : IWeakEventHandler<E>
where T : class
where E : EventArgs
{
private delegate void OpenEventHandler(T @this, object sender, E e);
private WeakReference _targetRef;
private OpenEventHandler _openHandler;
private EventHandler<E> _handler;
private UnregisterCallback<E> _unregister;
public WeakEventHandler(EventHandler<E> eventHandler, UnregisterCallback<E> unregister)
{
_targetRef = new WeakReference(eventHandler.Target);
_openHandler = (OpenEventHandler) Delegate.CreateDelegate(
typeof(OpenEventHandler), null, eventHandler.Method);
_handler = Invoke;
_unregister = unregister;
}
public void Invoke(object sender, E e)
{
T target = (T) _targetRef.Target;
if (target != null)
_openHandler.Invoke(target, sender, e);
else if (_unregister != null)
{
_unregister(_handler);
_unregister = null;
}
}
public EventHandler<E> Handler
{
get { return _handler; }
}
public static implicit operator EventHandler<E>(WeakEventHandler<T, E> weakEventHandler)
{
return weakEventHandler._handler;
}
}
public static class EventHandlerUtils
{
public static EventHandler<E> MakeWeak<E>(this EventHandler<E> eventHandler, UnregisterCallback<E> unregister)
where E : EventArgs
{
if (eventHandler == null)
throw new ArgumentNullException("eventHandler");
if (eventHandler.Method.IsStatic || eventHandler.Target == null)
throw new ArgumentException("Only instance methods are supported.", "eventHandler");
Type weakEventHandlerType = typeof(WeakEventHandler<,>).MakeGenericType(
eventHandler.Method.DeclaringType, typeof(E));
ConstructorInfo weakEventHandlerConstructor = weakEventHandlerType.GetConstructor(
new Type[] { typeof(EventHandler<E>), typeof(UnregisterCallback<E>) });
IWeakEventHandler<E> weakEventHandler = (IWeakEventHandler<E>)weakEventHandlerConstructor.Invoke(
new object[] { eventHandler, unregister });
return weakEventHandler.Handler;
}
}
Performance
Who wants to see a shoot out between strong event handlers and our weak event handler solution?Ok, so first up here’s the code for our benchmarks:
using System;
using System.Diagnostics;
class Performance
{
const int iterations = 1000000;
public static void Main()
{
Console.WriteLine("Running tests with {0} iterations", iterations);
RunTest("StrongEvent", new StrongEvent.Publisher());
RunTest("StrongEventDisposable", new StrongEventDisposable.Publisher());
RunTest("WeakEvent", new WeakEvent.Publisher());
}
public static void RunTest(string name, IPublisher publisher)
{
GC.Collect();
GC.WaitForPendingFinalizers();
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
publisher.CreateSubscriber();
publisher.DestroySubscriber();
}
stopwatch.Stop();
Console.WriteLine("{0} took {1} ms", name, stopwatch.ElapsedMilliseconds);
}
}
Results
Ouch! You win some, you lose some
The results are very interesting, in conclusion it really does pay to be a good .NET citizen, removing event handlers and disposing of an IDisposable wins here.
Microsoft have come up with the Weak Event Pattern for WPF: http://msdn.microsoft.com/en-us/library/aa970850.aspx
I wonder if Microsoft will come up with a better and more generic solution for weak event handlers in .NET 4.5.
Source
http://stevenhollidge.com/blog-source-code/WeakEventReference.zip
Note: The finalizers have been commented out in the code so as not to affect the performance tests.
No comments:
Post a Comment