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

 
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 
Excellent way to deal with memory leaks !
NewsMessage from the AuthormemberHedley Muscroft17 Jan '13 - 21:58 
Hi - I've noticed a few comments about this not working in WPF or Silverlight (even VB.NET). I'm afraid that I'm unable to assist as I don't use either of those technologies - the original article was meant purely for C# and WinForms.
 
If somebody is able to provide solutions for other environments/languages please post another CodeProject article and let me know and then I'll link to it from my article.
 
Many thanks!
Hedley Muscroft

QuestionHere is a Version wich supports WPFmemberjogibear998812 Dec '12 - 11:56 
I've created a version wich supports WPF...
 
But at the Moment it's not much tested but works at my App...
 

 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
 
namespace ControlPanel.SharedClasses
{
//--------------------------------------------------------------------------------
static public class EventHelper
{
static Dictionary> dicEventFieldInfos = new Dictionary>();
 
static BindingFlags AllBindings
{
get { return BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; }
}
 
//--------------------------------------------------------------------------------
static List GetTypeEventFields(Type t)
{
if (dicEventFieldInfos.ContainsKey(t))
return dicEventFieldInfos[t];
 
List lst = new List();
BuildEventFields(t, lst);
dicEventFieldInfos.Add(t, lst);
return lst;
}
 
//--------------------------------------------------------------------------------
static void BuildEventFields(Type t, List lst)
{
// 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 & 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.
foreach (EventInfo ei in t.GetEvents(AllBindings))
{
Type dt = ei.DeclaringType;
FieldInfo fi = dt.GetField(ei.Name, AllBindings);

if (fi != null)
lst.Add(fi);
else
{
FieldInfo fi2 = dt.GetField(ei.Name + "Event", AllBindings);
if (fi2 != null)
lst.Add(fi2);
}
}
 
// 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
/*
foreach (FieldInfo fi in t.GetFields(AllBindings))
{
EventInfo ei = t.GetEvent(fi.Name, AllBindings);
if (ei != null)
{
lst.Add(fi);
Console.WriteLine(ei.Name);
}
}
if (t.BaseType != null)
BuildEventFields(t.BaseType, lst);*/
}
 
//--------------------------------------------------------------------------------
static EventHandlerList GetStaticEventHandlerList(Type t, object obj)
{
MethodInfo mi = t.GetMethod("get_Events", AllBindings);
return (EventHandlerList)mi.Invoke(obj, new object[] { });
}
 
//--------------------------------------------------------------------------------
public static void RemoveAllEventHandlers(object obj) { RemoveEventHandler(obj, ""); }
 
//--------------------------------------------------------------------------------
public static void RemoveEventHandler(object obj, string EventName)
{
if (obj == null)
return;
 
Type t = obj.GetType();
List event_fields = GetTypeEventFields(t);
EventHandlerList static_event_handlers = null;
 
foreach (FieldInfo fi in event_fields)
{
if (EventName != "" && (string.Compare(EventName, fi.Name, true) != 0 && string.Compare(EventName + "Event", fi.Name, true) != 0))
continue;
 
if (fi.FieldType == typeof(System.Windows.RoutedEvent))
{
var wrt = fi.GetValue(obj);
 
PropertyInfo EventHandlersStoreType = t.GetProperty("EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic);
 
var EventHandlersStore = EventHandlersStoreType.GetValue(obj, null);
 
Type storeType = EventHandlersStore.GetType();
 
MethodInfo GetEventHandlers = storeType.GetMethod("GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public);

object[] Params = new object[] { wrt };

var ret = (System.Windows.RoutedEventHandlerInfo[])GetEventHandlers.Invoke(EventHandlersStore, Params);
 
EventInfo ei = t.GetEvent(fi.Name.Substring(0, fi.Name.Length - 5), AllBindings);
 
foreach (var routedEventHandlerInfo in ret)
{
ei.RemoveEventHandler(obj, routedEventHandlerInfo.Handler);
}
}
else if (fi.IsStatic)
{
// STATIC EVENT
if (static_event_handlers == null)
static_event_handlers = GetStaticEventHandlerList(t, obj);
 
object idx = fi.GetValue(obj);
Delegate eh = static_event_handlers[idx];
if (eh == null)
continue;
 
Delegate[] dels = eh.GetInvocationList();
if (dels == null)
continue;
 
EventInfo ei = t.GetEvent(fi.Name, AllBindings);
foreach (Delegate del in dels)
ei.RemoveEventHandler(obj, del);
}
else
{
// INSTANCE EVENT
EventInfo ei = t.GetEvent(fi.Name, AllBindings);
if (ei != null)
{
object val = fi.GetValue(obj);
Delegate mdel = (val as Delegate);
if (mdel != null)
{
foreach (Delegate del in mdel.GetInvocationList())
ei.RemoveEventHandler(obj, del);
}
}
}
}
}
 
//--------------------------------------------------------------------------------
}
}
GeneralMy vote of 4memberblackout_19 Sep '12 - 16:47 
Problems in wpf (.NET 4.0)
QuestionDoes not Work in Silverlightmembervk00024 Apr '12 - 11:34 
It does not work in Silverlight. Is there any way to fix that?
GeneralMy vote of 5memberIlka Guigova3 Nov '11 - 7:41 
I am going to download your solution to have a look at it as I am in a situation where I need to remove event handlers. Thank you.
QuestionDoesn't seem to work with ComboBox eventsmemberjoebarthib26 Aug '11 - 4:44 
Hi, thank you for sharing this code. I tried using it in order to remove some event handlers of a ComboBox. However, ComboBox events aren't recognized: in BuildEventFields, dt.GetField(ei.Name, AllBindings) always return null!
Do you have any idea why?
 
Thank you very much
Thibaud
AnswerRe: Doesn't seem to work with ComboBox events [modified]memberblackout_19 Sep '12 - 16:01 
For controls in WPF, is always returned null =(
 
I made this change to work:
 
    
            foreach (EventInfo ei in t.GetEvents(AllBindings))
            {
                Type dt = ei.DeclaringType;
                FieldInfo fi = dt.GetField(ei.Name, AllBindings);
             
 
                if (fi != null)
                    lst.Add(fi);
                else
                {
                    FieldInfo fi2 = dt.GetField(ei.Name + "Event", AllBindings);
                    if (fi2 != null)
                        lst.Add(fi2);
                }
            }
 
It also does not make the code work. Apparently the functioning of controls and events in WPF are different.

modified 20 Sep '12 - 0:39.

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 
I'm very sorry Kalai. I have no idea why your code isn't working with VB.NET - I'm afraid that I have never used VB.NET (and have no burning desire to do so) so I can't even tell you if your translation is correct.
 
However the code is CLS compliant, so why not compile it into a DLL and reference it from VB.NET as a DLL instead?
Hedley Muscroft

GeneralRe: In VB.Net this is not working properlymembergloierTech13 Jul '11 - 17:56 
Thank You so much. I'll try to use as DLL.
GeneralRe: In c#.Net this is not working properlymemberlawanya31 Jul '11 - 23:08 
i had tried testing the static class and it returns me always empty result.
can you help out with a sample solution
General[My vote of 2] Doesn't work in silverlightmemberMember 41591749 May '11 - 9:57 
Doesn't work in silverlight Frown | :(
 
using System.Management; (don't found)
GeneralRe: [My vote of 2] Doesn't work in silverlightmemberHedley Muscroft12 Jul '11 - 5:00 
The article doesn't say that it runs with Silverlight. Silverlight only supports a subset of the C# libraries (exclduing System.Management) so I don't really think it's fair to give it a low rating because it doesn't work with something it's not supposed to support.
 
Why not say "Doesn't work with Apple MAC" and give it ONE star?
Hedley Muscroft

QuestionWhere's the code?mvpPIEBALDconsult21 Dec '10 - 8:53 
You don't show and explain the code. I expect it's pretty good, show it off!
GeneralAmbigiousMatchExceptionmemberdrake8 Dec '10 - 1:15 
dt.GetField in BuildEventFields can raise an AmbigiousMatchException.
 
I encountered it when I was on Microsoft.Reporting.Winforms.ReportToolbar, while trying to fix the leak in the winforms reportviewer.
 
I solved it using the following code which is slightly less performant (in VB)
 
Private Shared Sub BuildEventFields(ByVal t As Type, ByVal lst As List(Of FieldInfo))
For Each ei As EventInfo In t.GetEvents(AllBindings)
Dim dt As Type = ei.DeclaringType
 
For Each fi As FieldInfo In dt.GetFields(AllBindings)
If fi.Name = ei.Name Then
lst.Add(fi)
End If
Next
Next
End Sub
 
I don't think I should break out of the for loop there, there might be multiple fields with the same name (which is why the exception was thrown in the first place).
GeneralMy vote of 5memberAhsanS21 Sep '10 - 6:50 
Recently i was using scitech's profiler and was wondering how to find the objects. U save my hours. Have a 5.
GeneralMy vote of 5memberBigdeak12 Sep '10 - 22:36 
Very nice article!
I didn't knew that, it's really useful Smile | :)
GeneralGreat stuff ...memberSushant Joshi9 Sep '10 - 4:09 
Nice work ..
Sucess is going from failure to failure without loss of enthusiasm.

GeneralMy vote of 5memberTiefeng You30 Aug '10 - 5:05 
Works great. By checking the delegate Target property, I can remove event handlers from a particular subscriber.
 
Tiefeng
GeneralI like it, you mention WeakEvents also, you may also like these links in my MVVM frameworkmvpSacha Barber23 Aug '10 - 22:13 
http://www.codeproject.com/KB/WPF/CinchII.aspx#WeakEventCreate[^]
 
http://www.codeproject.com/KB/WPF/CinchII.aspx#WeakEventSubscription[^]
 
Anyway for what you have done here, I give it a 5.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2010
  • Codeproject MVP 2008-2010
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralMy Vote of 5memberScruffyDuck23 Aug '10 - 19:57 
Thank you. I have an application that I have been developing for several years and through a lot of new releaseds. There has always been a memory leak and I have suspected event handlers. This gives me a way to deal with some of them without having to refactor (which I will do eventually).
Jon

GeneralDeclaringTypememberSteve Hansen23 Aug '10 - 1:44 
Can't you use the DeclaringType property on the EventInfo to query the correct Type for its private field?
GeneralRe: DeclaringTypememberHedley Muscroft23 Aug '10 - 7:57 
Excellent point Steve - I've updated the code and the article and credited you with that improvement. Many thanks!
Hedley Muscroft

GeneralMy vote of 5mvpPete O'Hanlon22 Aug '10 - 23:04 
Great job - especially for pointing out that the shortcomings of doing this. A truly professional article.

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

Permalink | Advertise | Privacy | Mobile
Web01 | 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