Introduction
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).
Simple example:
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 EventHandler
), 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.
Furthermore, when EventHandler
s / 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:
cEventHandler.RemoveAllEventHandlers(naughty_object);
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:
cEventHelper.RemoveEventHandler(naughty_object, "SomeEvent");
Simple as that.
How It Works
When you first call cEvent.RemoveAllEventHandlers
(or cEvent.RemoveEventHandler
), the class builds a list of System.Reflection.FieldInfo
objects for each of the Events belonging to that type
.
Why 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
FieldInfo
objects have been collected for a given type, they are cached in a
Dictionary
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 EventHandler
s.
For more information on what's going on inside the class, the main routine to look at is cEventHelper.RemoveEventHander(...)
.
Points of Interest
I learned something new: Type.GetEvent
(and Type.GetEvents
) will get all EventInfos
for the type AND its ancestors, while Type.GetField
(and Type.GetFields
) get only the FieldInfos
for the exact type.
The BindingFlags.FlattenHierarchy
doesn't help because that only works on PROTECTED & PUBLIC members and the FieldInfo
s we're getting are PRIVATE. That's why in the BuildEventFields(...)
routine, you'll see that after getting all the EventInfo
s, we have to get the 'DeclaringType
' before we can use Type.GetField
.
Closing Comments
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 EventHandler
s. However, if you're in a fix (like I was), this could certainly help out!
History
- 1.0 Initial release
- 1.1 Updated code (and article) to use
MemberInfo.DeclaringType
(thanks to Steve Hansen for pointing this out!)
Hedley is a Director of Pioneer Software Ltd, a small UK-based software company specialising in Windows application development.
He started programming back in the mid-80's in C++ and later specialised in Borland's C++ Builder and Delphi.
After several years as a contractor, he founded Pioneer Software Ltd in the mid-90's to concentrate on developing bespoke Windows applications.
Current 'language/environment of choice' is C# and VS2010.