I've recently been working on a pretty massive WinForms project which is very complex. We noticed that the application was 'leaking' memory (i.e. the memory usage was continually growing despite objects being correctly 'Disposed').
Having run the application through SciTech's excellent .NET Memory Profiler, we discovered the cause: "Direct EventHandler Roots".
One of the biggest causes of "memory leaks" in a .NET application is when an unintended reference keeps a Managed object alive (the other being Managed objects which hold Unmanaged resources).
A.SomeEvent += new EventHandler(B.SomeMethod);
Later, you finish with
B (but not with
A) and Dispose of it. However because
A is holding a reference to
B (via an
B will never get garbage collected while
A is still alive.
Big deal? Can't you just do:
A.SomeEvent -= new EventHandler(B.SomeMethod);
Absolutely! And that will de-couple the objects and allow them to be GC'd correctly.
HOWEVER, in the real world (and especially in big complex applications which have grown 'organically' over time) you can find yourself dealing with dozens of different object types and scores of object instances where the interplay between the classes via Delegates and EventHandlers can be nightmarish to work through.
EventHandlers / Delegates are assigned dynamically at runtime, it may be impossible to know at any one time what Delegates are assigned to what EventHandlers, thus making it impossible to use
-= to remove the appropriate delegate. And what about removing anonymous delegates? It all gets very hairy.
Of course, if you know from the outset that you're going to use this sort of "EventHandler-heavy" model, then you should really use Weak Referenced EventHandlers. (Please see John Stewien's excellent article about this for more information.)
Anyway, if you find yourself in a fix with an application which is leaking memory due to rooted
EventHandler references, and you don't have time to refactor the code or add
-= counterparts to all the
+='s, then this may help!
Using the Code
Attached is a simple
static class (
cEventHelper) for you to incorporate into your own code.
If you're using SciTech's .NET Memory Profiler (or something similar) and you uncover an object which is holding on to a reference via its EventHandlers, then simply call:
Alternatively, if you know exactly which event is causing the problem and you don't want to unhook all of the events, then you can call:
Simple as that.
How It Works
When you first call
cEvent.RemoveEventHandler), the class builds a list of
System.Reflection.FieldInfo objects for each of the Events belonging to that
FieldInfo objects and not
EventInfo objects? Because the underlying
FieldInfo object is required to get at the invocation list for the Event that it represents, hence that's the object that we're interested in.
Once all of the
objects have been collected for a given type, they are cached in a
for performance reasons (you could easily edit the code to remove the caching if you wish). Now it's just a matter of using Reflection to get the invocation list and removing all the assigned delegates.
This was (reasonably) easy to do for INSTANCE events, but you have to handle STATIC events differently. To be honest, it took me ages to work out how to do this and it basically involved tonnes of trial-and-error with various code snippets scattered around the internet. We got there in the end though and
cEventHelper will happily remove both INSTANCE and STATIC
For more information on what's going on inside the class, the main routine to look at is
Points of Interest
I learned something new:
Type.GetEvents) will get all
EventInfos for the type AND its ancestors, while
Type.GetFields) get only the
FieldInfos for the exact type.
BindingFlags.FlattenHierarchy doesn't help because that only works on PROTECTED & PUBLIC members and the
FieldInfos we're getting are PRIVATE. That's why in the
BuildEventFields(...) routine, you'll see that after getting all the
EventInfos, we have to get the '
DeclaringType' before we can use
Once again, I'd like to stress that I wouldn't recommend this approach if you have the luxury of starting a new project or if your project is small enough to refactor the code to use Weak Reference
EventHandlers. However, if you're in a fix (like I was), this could certainly help out!
- 1.0 Initial release
- 1.1 Updated code (and article) to use
MemberInfo.DeclaringType (thanks to Steve Hansen for pointing this out!)