Wow. That was a much harder nut to crack than I expected. It is possible to do this, but I'll warn you, it's a real mess. Ultimately, what you need to do is to get the Delegate
object for the event. Once you have that, removing the event handler is easy because you can just do something like this:
Dim d As [Delegate] = ' ... (we'll figure this out later)
For Each handler As [Delegate] In d.GetInvocationList()
RemoveHandler btnSave.Click, DirectCast(handler, EventHandler)
Next
However, getting the delegate for the event is particularly difficult. Reflection makes it easy to get an EventInfo
object for any desired event, but it does not provide a simple way to get the delegate for that event. Technically speaking, there is no way, for sure, to always get the delegate, since events encapsulate their delegate privately and they could be implemented in any custom way. But, since most events are implemented in the "normal" way, it is possible with reflection to get the private delegate hidden in the object. Of course, all of that will break if Microsoft ever decides to change the "normal" way in which it compiles events, but since that's the best option you've got, you're stuck with it.
To further complicate things, the event handlers for WinForm controls are implemented in a custom way (albeit, all consistently with each other), so to get the delegate for a control's event is even more complex. There is an excellent write-up on all of this on this page of Bob Powell's blog. It's too lengthy for me to repeat all of the information here, but I have used it to put together a VB.NET example of how to solve your particular situation:
Dim currType As Type = btnSave.GetType()
Dim eventFieldInfo As FieldInfo = Nothing
Do
eventFieldInfo = currType.GetField("EventClick", BindingFlags.Static Or BindingFlags.Instance Or BindingFlags.NonPublic)
If eventFieldInfo Is Nothing Then
currType = currType.BaseType
End If
Loop While eventFieldInfo Is Nothing
Dim ehl As EventHandlerList = DirectCast(btnSave.GetType().GetProperty("Events", BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.FlattenHierarchy).GetValue(btnSave), EventHandlerList)
Dim d As [Delegate] = ehl(eventFieldInfo.GetValue(btnSave))
For Each handler As EventHandler In d.GetInvocationList()
RemoveHandler btnSave.Click, handler
Next
See the link above for the explanation of why and how that works. However, as I said, this is quite ugly and could break in future versions of .NET. Therefore, if there is any other way for you to do this, it would be better to use those alternatives. For instance, if you have control over the code that adds the event handler, you could just keep a separate reference to that delegate so you could remove it later, for instance:
Dim myClickHandler As EventHandler = AddressOf btnSave_Click
AddHandler btnSave.Click, myClickHandler
' ...
RemoveHandler btnSave.Click, myClickHandler