You are on page 1of 6

Basic Instincts

Static Event Binding Using WithEvents


Ted Pattison

Contents
Using the WithEvents Keyword
Static Event-binding Magic
Using WithEvents Fields in Inherited Classes
Summing it Up

This month's Basic Instincts column builds upon my last three columns in which I introduced and explained
the fundamental concepts and syntax associated with delegates and events. Last month I showed you how to
design and write a simple class that defines and raises events. You also saw how to dynamically bind an event
handler to an event using the AddHandler keyword. This month I am going to discuss static event binding, an
alternative technique for registering an event handler.

Using the WithEvents Keyword


Programmers with previous experience in Visual Basic® may find that static event binding is fairly easy
because of its familiar syntax using the WithEvents keyword. Let me revisit the example of the BankAccount
class that I used in last month's column. This class defines an event as a public member and raises the event
from within a method definition, as shown here:

Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)

Class BankAccount
Public Event LargeWithdraw As LargeWithdrawHandler
Sub Withdraw(ByVal Amount As Decimal)
'*** send notifications if required
If (Amount > 5000) Then
RaiseEvent LargeWithdraw(Amount)
End If
'*** perform withdrawal
End Sub
End Class

Objects created from the BankAccount class expose the LargeWithdraw event. You can see that a BankAccount
object contains the logic to raise the LargeWithdraw event whenever a withdrawal is made for a value greater
than $5,000.
Imagine that you want to create a new class named AccountAuditor1 to act as an event listener using static
event binding. You can define a class as an event listener by adding one or more fields defined with the
WithEvents keyword:

Class AccountAuditor1
Private WithEvents account As BankAccount
'*** other members omitted
End Class

You should note that a field defined using the WithEvents keyword must be based on a class that defines one
or more instance events; otherwise it will cause a compile-time error. In this example, the field that is named
account can be defined using the WithEvents keyword because the BankAccount class defines an instance
event named LargeWithdraw.
The point of defining the account field with the WithEvents keyword is to allow methods defined within the
AccountAuditor1 class to be registered as event handlers for events raised by a BankAccount object. Now that
I've defined a WithEvents field, I'll create a method that will act as an event handler for the LargeWithdraw
event.
When you define a method that's going to be an event handler, it must have the appropriate calling signature.
For example, a method that's going to act as an event handler for the LargeWithdraw event must have a
calling signature that matches LargeWithdrawHandler. Look at the following class definition:
Class AccountAuditor1
Private WithEvents account As BankAccount
Sub Handler1(ByVal amount As Decimal) Handles account.LargeWithdraw
'*** handler method implementation
End Sub
End Class

In this example, I've added a new method named Handler1. As you can see, this handler method has a calling
signature that matches the delegate type LargeWithdrawHandler. Also notice that the definition of the
Handler1 method contains a Handles clause:

Sub Handler1(ByVal amount As Decimal) Handles account.LargeWithdraw

You should see that a Handles clause is based on a WithEvents field and an event. The Handles clause is
important because it provides a hint to the Visual Basic .NET compiler. More specifically, the presence of a
Handles clause triggers the Visual Basic .NET compiler to generate extra code that will create and register an
event handler. In this particular case, the compiler generates code to create an event handler of type
LargeWithdrawHandler that's bound to the Handler1 method. The compiler will then generate code to register
it with the BankAccount object that has been assigned to the account field.
Note that the Handles keyword is new in Visual Basic .NET. Earlier versions of Visual Basic required you to use
a specific naming convention for event handler methods. For example, Visual Basic 6.0 would require the
handler method in the previous example to be defined using the name account_LargeWithdraw. However, in
Visual Basic .NET the name of the handler method doesn't matter; what does matter is that the handler
method is defined with a Handles clause.
The real magic is performed by the Visual Basic .NET compiler when you assign an event source object to a
WithEvents field. I'm going to defer the discussion of how the compiler performs this magic until later. For
now, I'm going to make one more addition to AccountAuditor1 so the class can be used as an event listener.
A listener object needs an event source object. For example, it doesn't make sense to create an
AccountAuditor1 object unless you have a BankAccount object that's going to act as an event source.
Therefore, I'm going to add a constructor so that every AccountAuditor1 object is initialized with a
BankAccount object as its event source, like this:

Class AccountAuditor1
Private WithEvents account As BankAccount
Sub Handler1(ByVal amount As Decimal) Handles account.LargeWithdraw
'*** handler method implementation
End Sub
Sub New(ByVal SourceAccount As BankAccount)
Me.account = SourceAccount '*** triggers binding of event handler
End Sub
End Class

Note that this constructor assigns the SourceAccount parameter to the WithEvents field named account. This
is the line of code where the automatic binding of the event handler takes place. Now the AccountAuditor1
class can be used to create a listener object. For example, imagine you wrote the following application:

'*** create event source


Dim account1 As New BankAccount()

'*** create listener object and bind event handler


Dim listener1 As New AccountAuditor1(account1)

'*** do something that triggers event


account1.Withdraw(5001)

When the constructor of the AccountAuditor1 class executes, the Visual Basic .NET compiler has generated the
code to create an event handler object that's bound to the Handler1 method. The compiler also generates
code to register the event handler with the BankAccount object by calling the registration method
add_LargeWithdraw. Therefore, the Handler1 method will execute whenever the Withdraw method raises the
LargeWithdraw event.
You have just seen the fundamentals of how to create a listener object using static event binding. Figure 1
shows a complete application that uses static event binding to implement a callback design that is similar to
the other techniques you have seen over the last three Basic Instincts columns. You should note that static and
dynamic event binding are two different approaches that can often be used to achieve similar goals.

Figure 1 Event-based Design for Callback Notifications

Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)

Class BankAccount
Public Event LargeWithdraw As LargeWithdrawHandler
Sub Withdraw(ByVal Amount As Decimal)
'*** send notifications if required
If (Amount > 5000) Then
RaiseEvent LargeWithdraw(Amount)
End If
'*** perform withdrawal
End Sub
End Class

Class AccountAuditor1
Private WithEvents account As BankAccount
Sub Handler1(ByVal amount As Decimal) Handles account.LargeWithdraw
'*** handler method implementation
MsgBox("Handler1")
End Sub
Sub New(ByVal SourceAccount As BankAccount)
Me.account = SourceAccount ''*** triggers binding of event handler
End Sub
End Class

Class AccountAuditor2
Private WithEvents account As BankAccount
Sub Handler2(ByVal amount As Decimal) Handles account.LargeWithdraw
'*** handler method implementation
MsgBox("Handler2")
End Sub
Sub New(ByVal SourceAccount As BankAccount)
Me.account = SourceAccount '*** triggers binding of event handler
End Sub
End Class

Module MyApp
Sub Main()
'*** create bank account object
Dim account1 As New BankAccount()
'*** register event handlers
Dim listener1 As New AccountAuditor1(account1)
Dim listener2 As New AccountAuditor2(account1)
'*** do something that triggers callback
account1.Withdraw(5001)
End Sub
End Module

Now that you have seen both static and dynamic event binding, you might be wondering which one you
should use. Ideally, you should learn how to use both. The Visual Studio® .NET IDE uses static event binding
when you ask it to generate skeleton definitions for your event handler methods. Therefore, your
understanding of static event binding will be important when you are working with an event-driven
application framework such as Windows® Forms or ASP.NET Web Forms.
However, it's also important that you know how to use dynamic event binding, which I showed last month,
because of its flexibility. For example, static event binding can only be used in cases where the event source is
an object. It cannot be used when the event source is a class. In other words, static event binding can be used
with instance events but cannot be used with shared events. Dynamic event binding, on the other hand, will
allow you to bind to either shared events or instance events.
There are circumstances when the Visual Studio .NET IDE is not able to generate all the event handling code
you need for an application and you'll have to write the code to create and register event handlers by hand. In
cases like these, you will find that dynamic event binding is more straightforward and easier to use. After all, it
only takes a single AddHandler statement to create an event handler from a method and bind it to an event.
The only requirement is that the handler method have the calling signature that's required for the event in
question.
One more interesting thing to note is that static event binding is a special programming feature that is unique
to Visual Basic .NET. Other managed languages, such as C#, support dynamic event binding but do not
support the equivalent of static event binding. If you are going to switch between managed languages or you
need to port code from C# to Visual Basic .NET, you should consider relying on dynamic event binding.

Static Event-binding Magic


Now I'd like to discuss the low-level details of how the Visual Basic .NET compiler supports static event
binding. You can use static event binding without understanding all the details I am about to explain, but I'll
provide the details about what the compiler is doing to satisfy any curiosity you may have about how things
work behind the scenes.
Let's start by looking at what happens when you compile a class definition with a WithEvents field. Assume
that you have written the following class definition:

Class AccountAuditor
Private WithEvents account As BankAccount
'*** other members omitted
End Class

What happens when you compile this class? The Visual Basic .NET compiler generates the class definition
shown in ILDasm (see Figure 2). As you can see, the class definition generated by the compiler is very different
from the one you wrote. After it has been compiled, there is no longer a field named account. Instead, there is
a private field named _account and a read/write property named account. The compiler has also generated
special implementations for the account property's Set and Get methods. All the magic is in the Set method
implementation.

Figure 2 The Compiler's Class Definition


So what really happens when you assign an event source object to a WithEvents field? For example, what
happens when you assign a BankAccount object to the account field in the constructor of the AccountAuditor
class?

Class AccountAuditor
Private WithEvents account As BankAccount
Sub Handler1(ByVal amount As Decimal) Handles account.LargeWithdraw
*** handler method implementation
End Sub
Sub New(ByVal source As BankAccount)
Me.account = source '*** triggers call to add_LargeWithdraw
End Sub
End Class

It's important to see that you are really not assigning the event source object to a field. Instead, you are
assigning the event source object to a property which causes the Set method named set_account to execute.
The implementation for set_account is generated to create and register an event handler for each method
defined with the Handles clause. Figure 3 shows how the code you write gets translated by the compiler.

Figure 3 Static Event Binding


'*******************************************
'**** the code you write looks like this ***
'*******************************************
Class AccountAuditor
Private WithEvents account As BankAccount
Sub Handler1(ByVal amount As Decimal) Handles account.LargeWithdraw
*** handler method implementation
End Sub
Sub New(ByVal SourceAccount As BankAccount)
Me.account = SourceAccount
End Sub
End Class

'***************************************************
'*** the code that gets compiled looks like this ***
'***************************************************
Class AccountAuditor
Private _account As BankAccount
Private Property account() As BankAccount
Get
Return _account
End Get
'*** magic code starts here ***************************************
Set(ByVal Value As BankAccount)
If (Not _account Is Nothing) Then
_account.remove_LargeWithdraw(AddressOf Me.Handler1)
End If
If (Not Value Is Nothing) Then
_account = Value
_account.add_LargeWithdraw(AddressOf Me.Handler1)
End If
End Set
'*** magic code ends here *****************************************
End Property
Sub Handler1(ByVal amount As Decimal) Handles account.LargeWithdraw
'*** handler implementation
End Sub
Sub New(ByVal source As BankAccount)
Me.account = source '*** triggers call Set method
End Sub
End Class

Examine the code inside the Set method of the AccountAuditor class shown in Figure 3. This code checks to
see if there is an existing event source object being referenced by the _account field. If there is, the code
removes it by calling the unregistration method remove_LargeWithdraw. Next, the Set method checks the
Value parameter to see whether the assigned value is a valid reference to an event source object or a value of
Nothing. If the Value parameter holds a valid reference to an event source object, the Set method creates an
event handler bound to the Handler1 method and registers it by calling add_LargeWithdraw.
So, there's really no magic involved. When you assign a valid object reference to a WithEvents field, the
compiler generates code to create delegate objects and bind them to each method that has been defined with
a Handles clause. The compiler also generates the code to register these delegate objects by calling the event
registration methods defined by the notification source.
Note that you are not required to bind a listener object to an event source during initialization. You can assign
a value to a WithEvents field any time during the lifetime of a listener object. Consider the class definition in
Figure 4. With this new design, an AccountAuditor object will not be bound to an event source after it has
been initialized. However, you can call StartListening at any time to bind a listener object to an event source
object. Note however that a call to StartListening will disconnect the listener object from an existing event
source so that it can connect to the new one. A call to StopListening will disconnect the listener object from an
existing event source and, in addition, leave it in an unbound state.

Figure 4 Binding Listener Object Later

Class AccountAuditor
Private WithEvents account As BankAccount
Sub Handler1(ByVal amount As Decimal) Handles account.LargeWithdraw
'*** handler method implementation
End Sub
Sub StartListening(ByVal source As BankAccount)
Me.account = source '*** triggers call to add_LargeWithdraw
End Sub
Sub StopListening()
Me.account = Nothing '*** triggers call to remove_LargeWithdraw
End Sub
End Class

At this point it's fair to say that the Visual Basic .NET compiler is doing a good deal of work for you behind the
scenes when you use static event binding. Furthermore, this example only involved a single handler method.
In practice, you will often have many handler methods associated with a single WithEvents field.

Using WithEvents Fields in Inherited Classes


In the Microsoft® .NET Framework, it is common for a base class to raise events that are designed to be
handled by derived classes. The idea is that a derived class author can customize behavior by adding event
handlers to respond to events raised by a base class. This design technique provides an alternative to using
overridable methods. It's also an important technique for you to understand because it is used extensively by
application frameworks such as Windows Forms and ASP.NET.
Let's look at a simple example to see how this works. You have already seen that a Handles clause can be
written in terms of a WithEvents field. You can also write a Handles clause using the MyBase keyword to
handle an event defined within a base class, as shown in the following code:

Class CheckingAccount : Inherits BankAccount


Sub Handler1(ByVal amount As Decimal) Handles MyBase.LargeWithdraw
'*** handler method implementation
End Sub
End Class

Once again, the Visual Basic .NET compiler generates the code that's needed to create an event handler and
register it. However, the code for a base class event is generated in a slightly different fashion than the code
for an event associated with a WithEvents field. In this example, the Visual Basic .NET compiler adds the code
for binding to the LargeWithdraw method into the constructor of the CheckingAccount class.
There is one other thing you should keep in mind when working with a base class that exposes events. While a
derived class can handle a base class event, it cannot raise a base class event. Take a look at the following
example:

Class CheckingAccount : Inherits BankAccount


Sub SomeOtherMethod()
RaiseEvent LargeWithdraw(5001) '*** compile error
End Sub
End Class

Since the implementation for an event involves a private field, the event can only be raised from within the
class in which it is defined. Therefore, you will experience a compile-time error if you try to raise a base class
event using a RaiseEvent statement or if you attempt to access the private field by name.

Summing it Up
I've shown you the two different techniques for registering event handlers for events: dynamic event binding
and static event binding. To improve your knowledge of Visual Basic .NET, you must become comfortable with
both techniques.
In the next installment of this column I will continue my discussion of events by exploring some of the most
common delegate types and events in the Framework Class Libraries. In particular, I am going to focus on a
delegate type named System.EventHandler and show how it provides a foundation for most of the events you
will use in both Windows Forms and ASP.NET.

You might also like