Ever take the time to learn how events work in .Net? They are pretty basic, but unless you have taken the time to discover how they work, they may seem to be a bit like magic.
Events are a very powerful way to reduce coupling in your application. Events in .Net are based on the Observer pattern identified in the classic Gang-of-Four book, Design Patterns: Elements of Reusable Object-Oriented Software.
The first part of this article discusses how VB.Net hides some of the complexity of dealing with events. The second part explains how events work in .Net in general.
What VB.Net Hides From You
Compared to C#, event handling in VB.Net requires fewer lines of code to both publish and subscribe to events. However, this is because the VB.Net compiler hides the actual implementation from the developer. If you were to look at the code that the compiler generates, you would see that events in VB.Net work pretty very much the same as C#.
Let's create a couple of simple classes to use as an example. One is called MyPublisher and it defines an event called MyEvent. The other is called MySubscriber which creates an instance of MyPublisher and listens for MyEvent. You can download the example code here (in the download section of this website).
1: Public Class MyPublisher
2:
3: Public Sub ForceMyEvent()
4: OnMyEvent(EventArgs.Empty)
5: End Sub
6:
7: Public Event MyEvent(ByVal sender As Object, ByVal e As EventArgs)
8: Protected Overridable Sub OnMyEvent(ByVal e As EventArgs)
9: RaiseEvent MyEvent(Me, e)
10: End Sub
11:
12: End Class
13:
14: Public Class MySubscriber
15:
16: Public Sub New(ByVal publisher As MyPublisher)
17: mPublisher = publisher
18: End Sub
19:
20: Private WithEvents mPublisher As MyPublisher
21: Public ReadOnly Property Publisher() As MyPublisher
22: Get
23: Return mPublisher
24: End Get
25: End Property
26:
27: Private Sub mPublisher_MyEvent(ByVal sender As Object, ByVal e As EventArgs) Handles mPublisher.MyEvent
28: Console.WriteLine("Hello World!")
29: End Sub
30:
31: End Class
Pretty basic code, easy to understand, and one of the reasons why I like VB.Net. However, if you really want to understand what's going on, you should open this code up in Reflector. What you see is that the VB.Net compiler hides a lot of the complexity for you. What seems like a simple line of code actually get's broken down into many lines of code.
NOTE: Reflector shows some code that is automatically generated that doesn't have anything to do with events so I removed it from the examples, as well as it doesn't always generate the code correctly (for example, Reflector starts line 12 in the publisher with RaiseEvent which is incorrect).
1: Public Class MyPublisher
2:
3: Public Sub ForceMyEvent()
4: Me.OnMyEvent(EventArgs.Empty)
5: End Sub
6:
7: Public Delegate Sub MyEventEventHandler(ByVal sender As Object, ByVal e As EventArgs)
8: Public Event MyEvent As MyEventEventHandler
9: Protected Overridable Sub OnMyEvent(ByVal e As EventArgs)
10: If MyEventEvent IsNot Nothing Then
11: MyEventEvent(Me, e)
12: End If
13: End Sub
14:
15: End Class
If you are familiar with the way C# handles events, you will notice that VB.Net is compiled into something very similar. IMHO, VB.Net is more elegant in that you can declare the event and the delegate in a single line and you can raise the event in a single line.
The subscriber is a bit more interesting.
1: Public Class MySubscriber
2:
3: Public Sub New(ByVal publisher As MyPublisher)
4: Me.mPublisher = publisher
5: End Sub
6:
7: <AccessedThroughProperty("mPublisher")> _
8: Private _mPublisher As MyPublisher
9: Private Property mPublisher() As MyPublisher
10: Get
11: Return Me._mPublisher
12: End Get
13: Set(ByVal WithEventsValue As MyPublisher)
14: Dim handler As MyEventEventHandler = New MyEventEventHandler(AddressOf Me.mPublisher_MyEvent)
15: If (Not Me._mPublisher Is Nothing) Then
16: RemoveHandler Me._mPublisher.MyEvent, handler
17: End If
18: Me._mPublisher = WithEventsValue
19: If (Not Me._mPublisher Is Nothing) Then
20: AddHandler Me._mPublisher.MyEvent, handler
21: End If
22: End Set
23: End Property
24:
25: Public ReadOnly Property Publisher() As MyPublisher
26: Get
27: Return Me.mPublisher
28: End Get
29: End Property
30:
31: Private Sub mPublisher_MyEvent(ByVal sender As Object, ByVal e As EventArgs)
32: Console.WriteLine("Hello World!")
33: End Sub
34:
35: End Class
In the original code, we declare mPublisher using the WithEvents keyword. What this keyword does is change the field that we defined into a property and creates a new field by prefixing the original name of the field with an underscore. By doing this, when we set mPublisher in our code, what we are really doing is setting the property. This causes the AddHandler/RemoveHandler code to be invoked (if you develop in C#, this is a very useful pattern to follow).
How Events Work
In the Publisher class in the above example, MyEventEvent (line 11) is being called as if it were a method, but it is actually what is called a multi-cast delegate. A delegate is basically a reference to a method (in the case of events, this would be the event handler). A multi-cast delegate is essentially a list of delegates that can be called in the same way as a single delegate. Refer to the Code Project article, A Beginner's Guide to Delegates, for a good description of delegates and multi-cast delegates.
So basically when we raise an event, we are simply iterating through the list of event handlers and calling them one at a time (synchronously). All of the event handlers get called unless one of them throws an exception.
If you want more control over how events are raised, for example, you want to stop calling additional event handlers when one of them has handled or canceled the event, you can explicitly call each event handler in the list. Let's take our example from above and tweak it so that we are manually calling the methods.
1: Protected Overridable Sub OnMyEvent(ByVal e As CancelEventArgs)
2: If MyEventEvent IsNot Nothing Then
3: For Each handler As MyEventEventHandler In MyEventEvent.GetInvocationList()
4: handler(Me, e)
5: 'If e.Cancel Then Exit For
6: Next
7: End If
8: End Sub
In this example we are iterating through the list of delegates (the references to the event handlers) and calling each method in turn. If the event arguments were CancelEventArgs, we could check e.Cancel after calling the delegate and if it was true, we could exit the for loop. You can also use this to add asynchronous event handling to your project (if you do, make sure you document it well!).