Click here to Skip to main content
Click here to Skip to main content

Removing Event Handlers using Reflection

By , 23 Aug 2010
 

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 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:

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 EventHandlers.

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 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 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 EventHandlers. 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!)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Hedley Muscroft
Software Developer (Senior) Pioneer Software Ltd
United Kingdom United Kingdom
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberMarius Samoila23 Jan '13 - 12:49 
NewsMessage from the AuthormemberHedley Muscroft17 Jan '13 - 21:58 
QuestionHere is a Version wich supports WPFmemberjogibear998812 Dec '12 - 11:56 
GeneralMy vote of 4memberblackout_19 Sep '12 - 16:47 
QuestionDoes not Work in Silverlightmembervk00024 Apr '12 - 11:34 
GeneralMy vote of 5memberIlka Guigova3 Nov '11 - 7:41 
QuestionDoesn't seem to work with ComboBox eventsmemberjoebarthib26 Aug '11 - 4:44 
AnswerRe: Doesn't seem to work with ComboBox events [modified]memberblackout_19 Sep '12 - 16:01 
QuestionIn VB.Net this is not working properlymembergloierTech31 May '11 - 5:34 
Hi Hedley,
 
In my Vb.Net project, I have a MDI form. From MDI form I open all other forms by using the following coding
 
<pre> Private Sub E03ItemReceipt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles E03ItemReceipt.Click
 
mFrm = New Purchase_UI.E16ItemReceipt_UI(mGlobalVarGen)
mFrm.EntryFormProperty(Me)
mFrm.MdiParent = Me
mFrm.visible = False
mFrm.show()
End Sub</pre>
 
In the MDI form, in the MdiChildActivate event I call the RemoveEventHandlers method
 
<pre>Private Sub G01MainForm_MdiChildActivate(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MdiChildActivate
 
If ActiveMdiChild Is Nothing Then
ManageHandlers.cEventHelper.RemoveAllEventHandlers(mFrm)
ManageHandlers.cEventHelper.RemoveEventHandler(mFrm, "E03ItemReceipt_Click")
end if
end sub</pre>
 
I convert your c# code to VB as follows
 
<pre>Namespace ManageHandlers
'--------------------------------------------------------------------------------
Public NotInheritable Class cEventHelper
Private Sub New()
End Sub
Shared dicEventFieldInfos As New Dictionary(Of Type, List(Of FieldInfo))()
 
Private Shared ReadOnly Property AllBindings() As BindingFlags
Get
Return BindingFlags.IgnoreCase Or BindingFlags.[Public] Or BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.[Static]
End Get
End Property
 
'--------------------------------------------------------------------------------
Private Shared Function GetTypeEventFields(ByVal t As Type) As List(Of FieldInfo)
If dicEventFieldInfos.ContainsKey(t) Then
Return dicEventFieldInfos(t)
End If
 
Dim lst As New List(Of FieldInfo)()
BuildEventFields(t, lst)
dicEventFieldInfos.Add(t, lst)
Return lst
End Function
 
'--------------------------------------------------------------------------------
Private Shared Sub BuildEventFields(ByVal t As Type, ByVal lst As List(Of FieldInfo))
' Type.GetEvent(s) gets all Events for the type AND it's ancestors
' Type.GetField(s) gets only Fields for the exact type.
' (BindingFlags.FlattenHierarchy only works on PROTECTED &amp; PUBLIC
' doesn't work because Fieds are PRIVATE)
 
' NEW version of this routine uses .GetEvents and then uses .DeclaringType
' to get the correct ancestor type so that we can get the FieldInfo.
For Each ei As EventInfo In t.GetEvents(AllBindings)
Dim dt As Type = ei.DeclaringType
Dim fi As FieldInfo = dt.GetField(ei.Name, AllBindings)
If fi IsNot Nothing Then
lst.Add(fi)
End If
Next
 
' OLD version of the code - called itself recursively to get all fields
' for 't' and ancestors and then tested each one to see if it's an EVENT
' Much less efficient than the new code
'
'For Each fi As FieldInfo In t.GetFields(AllBindings)
 
' Dim ei As EventInfo = t.GetEvent(fi.Name, AllBindings)
' If ei IsNot Nothing Then
' lst.Add(fi)
' End If
'next
'If t.BaseType IsNot Nothing Then BuildEventFields(t.BaseType, lst)
 
End Sub
 
'--------------------------------------------------------------------------------
Private Shared Function GetStaticEventHandlerList(ByVal t As Type, ByVal obj As Object) As EventHandlerList
Dim mi As MethodInfo = t.GetMethod("get_Events", AllBindings)
Return DirectCast(mi.Invoke(obj, New Object() {}), EventHandlerList)
End Function
 
'--------------------------------------------------------------------------------
Public Shared Sub RemoveAllEventHandlers(ByVal obj As Object)
RemoveEventHandler(obj, "")
End Sub
 
'--------------------------------------------------------------------------------
Public Shared Sub RemoveEventHandler(ByVal obj As Object, ByVal EventName As String)
If obj Is Nothing Then
Return
End If
 
Dim t As Type = obj.[GetType]()
Dim event_fields As List(Of FieldInfo) = GetTypeEventFields(t)
Dim static_event_handlers As EventHandlerList = Nothing
 
<b>For Each fi As FieldInfo In event_fields</b>
If EventName &lt;&gt; "" AndAlso String.Compare(EventName, fi.Name, True) &lt;&gt; 0 Then
Continue For
End If
 
' After hours and hours of research and trial and error, it turns out that
' STATIC Events have to be treated differently from INSTANCE Events...
If fi.IsStatic Then
' STATIC EVENT
If static_event_handlers Is Nothing Then
static_event_handlers = GetStaticEventHandlerList(t, obj)
End If
 
Dim idx As Object = fi.GetValue(obj)
Dim eh As [Delegate] = static_event_handlers(idx)
If eh Is Nothing Then
Continue For
End If
 
Dim dels As [Delegate]() = eh.GetInvocationList()
If dels Is Nothing Then
Continue For
End If
 
Dim ei As EventInfo = t.GetEvent(fi.Name, AllBindings)
For Each del As [Delegate] In dels
ei.RemoveEventHandler(obj, del)
Next
Else
' INSTANCE EVENT
Dim ei As EventInfo = t.GetEvent(fi.Name, AllBindings)
If ei IsNot Nothing Then
Dim val As Object = fi.GetValue(obj)
Dim mdel As [Delegate] = TryCast(val, [Delegate])
If mdel IsNot Nothing Then
For Each del As [Delegate] In mdel.GetInvocationList()
ei.RemoveEventHandler(obj, del)
Next
End If
End If
End If
Next
End Sub
 
'--------------------------------------------------------------------------------
End Class
End Namespace
</pre>
 

In the procedure RemoveAllEventHandlers, event_fields is always nothing , and the execution does not enter into "For Each fi As FieldInfo In event_fields " loop and no handlers are removed and also the object not disposed.
 
Kindly help me to solve my problem.
 
Regards
kalai
AnswerRe: In VB.Net this is not working properlymemberHedley Muscroft12 Jul '11 - 5:03 
GeneralRe: In VB.Net this is not working properlymembergloierTech13 Jul '11 - 17:56 
GeneralRe: In c#.Net this is not working properlymemberlawanya31 Jul '11 - 23:08 
General[My vote of 2] Doesn't work in silverlightmemberMember 41591749 May '11 - 9:57 
GeneralRe: [My vote of 2] Doesn't work in silverlightmemberHedley Muscroft12 Jul '11 - 5:00 
QuestionWhere's the code?mvpPIEBALDconsult21 Dec '10 - 8:53 
GeneralAmbigiousMatchExceptionmemberdrake8 Dec '10 - 1:15 
GeneralMy vote of 5memberAhsanS21 Sep '10 - 6:50 
GeneralMy vote of 5memberBigdeak12 Sep '10 - 22:36 
GeneralGreat stuff ...memberSushant Joshi9 Sep '10 - 4:09 
GeneralMy vote of 5memberTiefeng You30 Aug '10 - 5:05 
GeneralI like it, you mention WeakEvents also, you may also like these links in my MVVM frameworkmvpSacha Barber23 Aug '10 - 22:13 
GeneralMy Vote of 5memberScruffyDuck23 Aug '10 - 19:57 
GeneralDeclaringTypememberSteve Hansen23 Aug '10 - 1:44 
GeneralRe: DeclaringTypememberHedley Muscroft23 Aug '10 - 7:57 
GeneralMy vote of 5mvpPete O'Hanlon22 Aug '10 - 23:04 
GeneralTake fivememberSyed Saqib Ali Tipu21 Aug '10 - 18:54 
JokeRe: Take fivememberaspdotnetdev21 Aug '10 - 20:41 
GeneralMy vote of 5memberJohn Simmons / outlaw programmer21 Aug '10 - 0:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 23 Aug 2010
Article Copyright 2010 by Hedley Muscroft
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid