Thomas Alva Edison's light bulb VBInfoZine Home
   An ordinary VB developer shares his own successes and failures
   FREE to registered subscribers.  

Advanced reflection - BindEventsByName.

.NET Reflection used for dynamic event binding.

When programming user interfaces in .NET, I've been using events extensively to implement the Observer design pattern.

However, as the number of events grows, the act of adding and removing event handlers might become tedious. With a little help of naming convention and .NET Reflection, one can shrink the numerous Add/RemoveHandler calls to just one line of code.

As an example, let's have the following Block class, which represents the Subject in the Observer pattern terminology (the code is taken from the project described in the article My first .NET project ):

Public Class Block
  ...
  Public Event CaptionChanging(...)
  Public Event CaptionChanged(...)

  Public Event ColorChanging(...)
  Public Event ColorChanged(...)

  Public Event IdChanging(...)
  Public Event IdChanged(...)
 
  ...
End Class
The various Observers can use the WithEvents keyword, which handles the event registration and unregistration automatically:
Public Class SomeObserver
  Private WithEvents _Block As Block
  ...
  Private Sub _Block_CaptionChanging(...) _
    Handles Block.CaptionChanging
  ...
End Class
However, when you want to handle events for a local Block reference, you have to use the Add/RemoveHandler statements (details here). When the number of events is large, writing the Add/RemoveHander calls can become tedious and error prone.

You can avoid the numerous Add/RemoveHandler calls by employing a simple convention for naming your event handlers (this is recommended anyway). For example I've been using the pattern On<EventName> - OnCaptionChanging, OnColorChanging...you've got the picture. With the little help of reflection, instead of writing this:

Dim B As New Block()
AddHandler B.ColorChanging, AddressOf Me.OnColorChanging
AddHandler B.ColorChanged, AddressOf Me.OnColorChanged
AddHandler B.IdChanging, AddressOf Me.OnIdChanging
AddHandler B.IdChanged, AddressOf Me.OnIdChanged
You can write this:
Dim B As New Block()
Utility.BindEventsByName(B, Me, True, "On", String.Empty)
Here is the (now famous :-)) BindEventsByName method:
Public Class Utility
  Public Shared Sub BindEventsByName( _
   ByVal sender As Object, _
   ByVal receiver As Object, _
   ByVal bind As Boolean, _
   ByVal handlerPrefix As String, _
   ByVal handlerSufix As String)
 
    ' Get the sender's (Subject) public events.
    Dim SenderEvents() As System.Reflection.EventInfo = sender.GetType().GetEvents()
 
    ' Get the receiver's type and lookup its public
    ' methods matching the naming convention:
    '  handlerPrefix<EventName>handlerSufix
    Dim ReceiverType As Type = receiver.GetType() 
    Dim E As System.Reflection.EventInfo
    Dim Method As System.Reflection.MethodInfo
    For Each E In SenderEvents
      Method = ReceiverType.GetMethod( _
        handlerPrefix & E.Name & handlerSufix, _
        System.Reflection.BindingFlags.IgnoreCase Or _
        System.Reflection.BindingFlags.Instance Or _
        System.Reflection.BindingFlags.NonPublic)

      If Not Method Is Nothing Then
        Dim D As System.Delegate = System.Delegate.CreateDelegate(E.EventHandlerType, receiver, Method.Name)
        If bind Then
          E.AddEventHandler(sender, D)
        Else
          E.RemoveEventHandler(sender, D)
        End If
      End If
    Next
  End Sub
End Class
*** Important note ***

The code doesn't check that the event handler signatures match the event delegates (it would be much more complex in that case). When the signatures don't match, the call to CreateDelegate will throw System.ArgumentException at runtime. This is the price you pay for the convenience.

Is it worth it?

It depends. As usually, you have to decide for yourself. ;-)

 ©2003-2008 Palo Mraz. All Rights Reserved.   See my 'new browser window' policy