in

Redwerb

Tools, tips, and techniques for software developers.

Redwerb

Tools, tips, and techniques for software developers.

How Things Work: Events in .Net

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

Published Apr 20 2008, 01:59 PM by Brian Brewder
Filed under:

Comments

 

Myron said:

Nice summary article.  Recently I've been doing some java, and while there are a few things I prefer about the java platform compared to .NET, I've got to say that after being spoiled with the way .NET (and VB.NET in particular) handles events, the java way is a royal PITA.  In .NET they took the observer pattern and baked it right onto the CLR and the languages themselves.  In java, you've got to manually implement the Observer interface on your subscriber object, and your publisher object has to derive from the Observable base class.  With java's lack of multiple inheritance, you're in trouble if you have a class that derives from some base class, and you want to add events to it.  You either create your own observer/observable interfaces and implement those, or find a way to accomplish what you want to do without events.  I googled the issue to see how other java developers addressed this issue, and the typical response I found was "you're not following good object-oriented design principles if you run into this problem.  Fix your design."  Really?  You can't imagine ever having a base class, another derived class, and wanting to make the derived class observable?  It boggles the mind.  

That said, I think that the way VB.NET handles events is a bit *too* magical.  I didn't have a clue how events worked when I first started working with VB.NET, and I'm sure I created more than my fair share of memory leaks when I forgot to desubscribe from an event (or set the WithEvents field to Nothing) after finishing with an object in the cases where this is necessary.  Actually, "forgot" isn't accurate--I didn't even realize I needed to until we had memory leak issues shortly before one of our releases and we discussed this and addressed it.  I like how C# makes you more aware of what's going on with the object references, at least for when you first start working with events.  Now that I do understand how events work, I like the simplicity VB.NET provides :).  I just think it may not be best for new .NET developers to use VB.NET first, until they understand how events work and when they need to desubscribe from events to prevent memory leaks.

April 20, 2008 11:06 PM

Leave a Comment

(required)  
(optional)
(required)  
Add

About Brian Brewder

I'm a software engineer in Kirkland, WA. I have been developing with .Net since 2002. My main area of focus has been designing and implementing a UI framework for an ERP system. Before I got into .Net, I developed for several years in a variety of languages and platforms including mostly ASP, though I've also developed applications for both Palm and Pocket PC devices.

I received my degree in Computing and Software System from the University of Washington in 1999. I have also completed a certificate course in Object-Oriented Analysis and Design Using UML, also from the University of Washington, in 2005.

Copyright Brian Brewder, 2007. All rights reserved.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems