How Things Work: Events in .Net

by Brian Brewder April 20, 2008 13:59

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!).

Tags:

Powered by BlogEngine.NET 1.6.0.0

About the author

I've been a software developer since 1999 and have been working with .Net since 2002. I love creating software, playing with productivity tools, and improving the process of software development. I hope you enjoy my blog. Please feel free to leave comments or contact me, I would love to hear from you.