In order to ensure that our events behave as intended we can use interfaces to control access, the classes that subscribe to the events, and the event registration process. To do this, we can write "observer"-type interfaces for notification, subscription and un-subscription to events because the VB.Net event model is based on the Observer GOF pattern.
If you are interested in the relation between the Observer pattern and the VB.Net event model, or if you don't have a clear understanding of events, check out my article on events here.
If you are new to interfaces, check out my article on interface based development here.
Event Publication
Events can be exposed publicly this way:
| Public Class Publisher Public Event SomeEvent As SomeEventHandler Delegate Sub SomeEventHandler(ByVal pub As Publisher) Public Sub FireTheEvent() If Nothing <> SomeEvent Then SomeEvent.Invoke(Me) End If End Sub 'FireTheEvent End Class 'Publisher |
Events can also be exposed through the event properties, using the "add" and "remove" keywords, as in the class below:
| Public Class Publisher Private Event m_SomeEvent As SomeEventHandler Delegate Sub SomeEventHandler(ByVal pub As Publisher) Public Sub FireTheEvent() If Nothing <> m_SomeEvent Then m_SomeEvent.Invoke(Me) End If End Sub 'FireTheEvent End Class 'Publisher |
If you choose to publish your events publically, I would recommend using the "add" and "remove" keywords for a couple of reasons.
- For code consistency, your event publication should follow the pattern used to expose the private member variables.
| Public Class [MyClass] Private m_number As Integer Public Property Number() As Integer Get Return m_number End Get Set(ByVal value As Integer) m_number = value End Set End Property Delegate Sub MyEventDelegate() Private Event m_event As MyEventDelegate End Class '[MyClass] |
-
If you need to implement the same method explicitly and in addition need to expose it implicitly through an interface member, you will need to use the event properties.
| Delegate Sub MyEventDelegate() Public Interface MyEventInterface Event MyEvent As MyEventDelegate End Interface 'MyEventInterface Public Class [MyClass] Inherits MyEventInterface Private m_number As Integer Public Property Number() As Integer Get Return m_number End Get Set(ByVal value As Integer) m_number = value End Set End Property Private Event m_event As MyEventDelegate End Class '[MyClass] |
The Problem With Public Events
One problem with exposing the event publicly on the class or through the interface is that we don't have control over who actually subscribes to the event. If we are purposely exposing the event so anyone and anything can register, that's one thing. However, this conflicts with one of the primary purposes of an interface, which is to provide behavioral contracts between objects.
Think of it this way. Let's say we are inviting our family over for a nice dinner and leave a message on voice mail letting them know of the plans. Alternatively, we could post a large sign outside our front door saying "FREE FOOD!!! - Just come on in. Dinner served at 8 pm." We won't know who is going to show up but I bet there will be some unexpected guests for dinner.
In the first case, we know who is coming to dinner and want some private time together with our family. On the other hand, if we are truly giving away free food to any passerby, the second option may be best for us and we can publish our dinner event publicly.
Publish Events Defensively.
In order to have a little more privacy over our event publication dinner we can leave the event as an implementation tool alone and expose it's functionality through interfaces. If we follow this route, it leads us to code that looks a lot more like the GOF observer pattern.
Our event publisher will expose functions to register and un-register an object to our event and to notify the observing members. We will expose all of this through the interface.
| Public Interface IEventPublisher Sub RegisterListener(ByVal listener As IEventListener) Sub UnregisterListener(ByVal listener As IEventListener) Sub NotifyListeners() End Interface 'IEventPublisher |
As a side note, if we are using the 2.0 framework, we could accomplish the same thing using generics and have a bit more type safety.
| Public Interface IEventPublisher2 Sub RegisterListener<T>(T listener) where T Implements IeventListener Sub UnregisterListener<T>(T listener) where T Implements IEventListener Sub NotifyListeners() End Interface |
Our event "listener" interface should preface all of the event driven methods using "On" to make our code more readable and understandable (Whenever you see "On" you should automatically think "there is an event here somewhere").
| Public Interface IEventListener Sub OnNotification(ByVal publisher As IEventPublisher) End Interface 'IEventListener |
Next, we define our publisher. Notice the delegate and event are both implemented as private member variables, thus removing the large "FREE FOOD" sign from the front door.
| Public Class Publisher Implements IEventPublisher Delegate Sub m_eventHandler(ByVal publisher As IEventPublisher) Private Event m_event As m_eventHandler Public Sub RegisterListener(ByVal listener As IEventListener) AddHandler m_event, AddressOf listener.OnNotification End Sub 'RegisterListener Public Sub UnregisterListener(ByVal listener As IEventListener) m_event -= New m_eventHandler(listener.OnNotification) End Sub 'UnregisterListener Public Sub NotifyListeners() If Nothing <> m_event Then m_event(Me) End If End Sub 'NotifyListeners End Class 'Publisher |
And our Listener class...
| Public Class Listener Implements IEventListener Public Sub OnNotification(ByVal publisher As IEventPublisher) Console.WriteLine(String.Format("{0} fired an event", publisher.GetType().ToString())) End Sub 'OnNotification End Class 'Listener |
And finally we can wire it up:
| Class Main Public Sub Run() Dim list As New Listener() Dim pub As New Publisher() pub.RegisterListener(list) pub.NotifyListeners() End Sub 'Run End Class 'Main |
In Conclusion To avoid unwanted guests are you dinner party, don't advertise it publicly. Also, if you don't want uninvited event listeners but want to take advantage of VB.Net event functionality -- keep the events to yourself and hide them in the class. This will also make for clearer, more maintainable code.
Until next time,
Happy coding
NOTE: THIS ARTICLE IS CONVERTED FROM C# TO VB.NET USING A CONVERSION TOOL. ORIGINAL ARTICLE CAN BE FOUND ON C# CORNER (http://www.c-sharpcorner.com/).