Tracer Tracer - 4 months ago 11
Vb.net Question

VB.NET - Passing an event as a parameter

I need to pass an event as a parameter to a function. Is there a way of doing this?

The reason is that I have a sequence of two lines of code that is littered all over my program, where I dynamically remove the handler to an event, and then set the handler again. I'm doing this for several different events and event handlers, so I've decided to write a function that does this.

As an example, let's say I have a combobox in my code called combobox1, and I have the handler called indexChangedHandler. In several places of my code, I have the following two lines:

RemoveHandler combobox1.SelectedIndexChanged, AddressOf indexChangedHandler
AddHandler combobox1.SelectedIndexChanged, AddressOf indexChangedHandler


Now, I don't want to keep on repeating the above two lines of code (or similar) all over my program, so I'm looking for a way to do this:

Private Sub setHandler(evt As Event, hndler As eventhandler)
RemoveHandler evt, hndler
AddHandler evt, hndler
End Sub


so that everywhere where those two lines of code(or similar) occur in my program, I can just replace them with:

setHandler(combobox1.SelectedIndexChanged, AddressOf indexChangedHandler)


So far, the "evt as Event" part of the argument of the setHandler function is giving an error.

P.S: I've asked this question on a couple of other forums and keep getting asked why I would want to set the handler immediately after removing it. The reason is because dynamically adding an event handler n-times causes the handler to be executed n-times when the event occurs. To avoid this, that is, to ensure that the handler is executed just once when the event occurs, I first remove the handler each time I want to add the handler dynamically.

You might be asking why the handler would be added several times in the first place... The reason is because I add the handler only after a particular event, say E1, in my form has occurred (I add the handler within the handler of event E1). And event E1 can occur several times within my form. If I do not remove the handler each time before adding it again, the handler gets added and thus executed several times.

Whatever the case, the processing occurring within the function is not of ultimate importance to me at this time, but rather just the means of passing an event as a parameter.

Answer

Of course you can pass events around... Well you can pass Action(Of EventHandler) which can do what you want.

If you have a class that defines an event like so:

Public Class ComboBox
    Public Event SelectedIndexChanged As EventHandler   
End Class

Given an instance of ComboBox you can then create add & remove handler actions like so:

Dim combobox1 = New ComboBox()

Dim ah As Action(Of EventHandler)
    = Sub (h) AddHandler combobox1.SelectedIndexChanged, h
Dim rh As Action(Of EventHandler)
    = Sub (h) RemoveHandler combobox1.SelectedIndexChanged, h

(Now, this is VB.NET 4.0 code, but you can do this in 3.5 using AddressOf and a little more mucking about.)

So if I have a handler Foo:

Public Sub Foo(ByVal sender as Object, ByVal e As EventArgs)
    Console.WriteLine("Over here with Foo!")
End Sub

And a Raise method on ComboBox I can now do this:

ah(AddressOf Foo)
combobox1.Raise()
rh(AddressOf Foo)

This writes the message "Over here with Foo!" as expected.

I can also create this method:

Public Sub PassActionOfEventHandler(ByVal h As Action(Of EventHandler))
    h(AddressOf Foo)
End Sub

I can pass around the event handler actions like so:

PassActionOfEventHandler(ah)
combobox1.Raise()
PassActionOfEventHandler(rh)

Which again writes the message "Over here with Foo!".

Now, one issue that might be a problem is that you can accidentally swap the add and remove event handler delegates in code - after all they are the same type. So it is easy to just define strongly-typed delegates for the add and remove actions like so:

Public Delegate Sub AddHandlerDelegate(Of T)(ByVal eh as T)
Public Delegate Sub RemoveHandlerDelegate(Of T)(ByVal eh as T)

The code to define the delegate instances doesn't change except for the delegate types:

Dim ah As AddHandlerDelegate(Of EventHandler)
    = Sub (h) AddHandler combobox1.SelectedIndexChanged, h
Dim rh As RemoveHandlerDelegate(Of EventHandler)
    = Sub (h) RemoveHandler combobox1.SelectedIndexChanged, h

So this now lets you be very creative. You can define a function that will take the add handler delegate, the remove handler delegate and a event handler, which will wire-up an event handler, and then return to you an IDisposable that you can use later to remove the handler without needing to retain a reference to the event handler. This is handy for using Using statements.

Here's the signature:

Function SubscribeToEventHandler(
    ByVal h as EventHandler,
    ByVal ah As AddHandlerDelegate(Of EventHandler),
    ByVal rh As RemoveHandlerDelegate(Of EventHandler)) As IDisposable

So given this function I can now do this:

combobox1.Raise()
Using subscription = SubscribeToEventHandler(AddressOf Foo, ah, rh)
    combobox1.Raise()
    combobox1.Raise()
End Using
combobox1.Raise()

And this writes the message "Over here with Foo!" only twice. The first and last Raise calls are outside of the subscription to the event handler.

Enjoy!