Tải bản đầy đủ (.pdf) (109 trang)

Pro VB 2005 and the .NET 2.0 Platform Second Edition phần 4 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.27 MB, 109 trang )

CHAPTER 9 ■ WORKING WITH INTERFACES AND COLLECTIONS 279
■Source Code The CollectionTypes project can be found under the Chapter 9 subdirectory.
System.Collections.Specialized Namespace
In addition to the types defined within the System.Collections namespace, you should also be aware
that the .NET base class libraries provide the System.Collections.Specialized namespace, which
defines another set of types that are more (pardon the redundancy) specialized. For example,
the StringDictionary and ListDictionary types each provide a stylized implementation of the
IDictionary interface. Table 9-5 documents the key class types.
Table 9-5. Types of the System.Collections.Specialized Namespace
Member of System.Collections.Specialized Meaning in Life
CollectionsUtil Creates collections that ignore the case in strings.
HybridDictionary Implements IDictionary by using a ListDictionary
while the collection is small, and then switching
to a Hashtable when the collection gets large.
ListDictionary Implements IDictionary using a singly linked list.
Recommended for collections that typically contain
ten items or fewer.
NameValueCollection Represents a sorted collection of associated String
keys and String values that can be accessed either
with the key or with the index.
StringCollection Represents a collection of strings.
StringDictionary Implements a hashtable with the key strongly typed
to be a string rather than an object.
StringEnumerator Supports a simple iteration over a StringCollection.
Summary
An interface can be defined as a named collection of abstract members. Because an interface does
not provide any implementation details, it is common to regard an interface as a behavior that may
be supported by a given type. When two or more classes implement the same interface, you are able
to treat each type the same way (via interface-based polymorphism) even if the types are defined
within unique class hierarchies.
VB 2005 provides the Interface keyword to allow you to define a new interface. As you have seen,


a type can support as many interfaces as necessary using the Implements keyword. Furthermore, it
is permissible to build interfaces that derive from multiple base interfaces.
In addition to building your custom interfaces, the .NET libraries define a number of framework-
supplied interfaces. As you have seen, you are free to build custom types that implement these
predefined interfaces to gain a number of desirable traits such as cloning, sorting, and enumerat-
ing. Finally, you spent some time investigating the stock collection classes defined within the
System.Collections namespace and examining a number of common interfaces used by the collection-
centric types.
5785ch09.qxd 3/31/06 10:50 AM Page 279
5785ch09.qxd 3/31/06 10:50 AM Page 280
CHAPTER 10
■ ■ ■
Callback Interfaces, Delegates,
and Events
Up to this point in the text, every application you have developed added various bits of code to
Main(), which, in some way or another, sent requests to a given object by invoking its members.
However, you have not yet examined how an object can talk back to the entity that created it. In
most programs, it is quite common for objects to engage in a two-way conversation through the use
of callback interfaces, events, and other programming constructs. Although we most often think of
events in the context of a GUI environment (for example, handling the Click event of a button or
detecting mouse movement), the truth of the matter is events can be used to allow any two objects
in memory to communicate (visible or not).
This chapter opens by examining how interface types may be used to enable callback functionality.
Although the .NET event architecture is not directly tied to interface-based programming techniques,
callback interfaces can be quite useful given that they are language and architecture neutral.
Next, you learn about the .NET delegate type, which is a type-safe object that “points to” other
method(s) that can be invoked at a later time. As you will see, .NET delegates are quite sophisticated, in
that they have built-in support for multicasting and asynchronous (e.g., nonblocking) invocations.
Once you learn how to create and manipulate delegate types, you then investigate a set of VB 2005
keywords (Event, Handles, RaiseEvent, etc.) that simplify the process of working with delegate types

in the raw. Finally, this chapter examines a new language feature provided by Visual Basic 2005,
specifically the ability to build “custom events” in order to intercept the process of registering with,
detaching from, and sending an event notification.
■Note You will be happy to know that the event-centric techniques shown in this chapter are found all throughout
the .NET platform. In fact, when you are handling Windows Forms or ASP.NET events, you will be using the exact
same syntax.
Using Interfaces As a Callback Mechanism
As you have seen in the previous chapter, interfaces can be used to define a behavior that may be
supported by various types in your system. Beyond using interfaces to establish polymorphism
across hierarchies, interfaces may also be used as a callback mechanism. This technique enables
objects to engage in a two-way conversation using an agreed upon set of members.
To illustrate the use of callback interfaces (also termed event interfaces), let’s retrofit the now
familiar Car type (first defined in Chapter 6) in such a way that it is able to inform the caller when
the engine is about to explode (when the current speed is 10 miles below the maximum speed) and
has exploded (when the current speed is at or above the maximum speed). The ability to send and
receive these events will be facilitated with a custom interface named IEngineStatus:
281
5785ch10.qxd 3/31/06 10:51 AM Page 281
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS282
' The callback interface.
Public Interface IEngineStatus
Sub AboutToBlow(msg As String)
Sub Exploded(msg As String)
End Interface
In order to keep an application’s code base as flexible and reusable as possible, callback interfaces
are not typically implemented directly by the object interested in receiving the events, but rather by
a helper object called a sink object. Assume we have created a class named CarEventSink that imple-
ments IEngineStatus by printing the incoming messages to the console. As well, our sink will also
maintain a string used as a textual identifier. As you will see, it is possible to register multiple sink
objects for a given event source; therefore, it will prove helpful to identify a sink by name. This being

said, consider the following implementation:
' Car event sink.
Public Class CarEventSink
Implements IEngineStatus
Private name As String
Public Sub New(ByVal sinkName As String)
name = sinkName
End Sub
Public Sub AboutToBlow(ByVal msg As String) _
Implements IEngineStatus.AboutToBlow
Console.WriteLine("{0} reporting: {1}", name, msg)
End Sub
Public Sub Exploded(ByVal msg As String) _
Implements IEngineStatus.Exploded
Console.WriteLine("{0} reporting: {1}", name, msg)
End Sub
End Class
Now that you have a sink object that implements the event interface, your next task is to pass
a reference to this sink into the Car type. The Car holds onto this object and makes calls back on the
sink when appropriate. In order to allow the Car to receive the caller-supplied sink reference, we will
need to add a public helper member to the Car type that we will call Connect(). Likewise, to allow the
caller to detach from the event source, we will define another helper method on the Car type named
Disconnect(). Finally, to enable the caller to register multiple sink objects (for the purposes of
multicasting), the Car now maintains an ArrayList to represent each outstanding connection. Here
are the relevant updates to the Car type:
' This iteration of the Car type maintains a list of
' objects implementing the IEngineStatus interface.
Public Class Car
' The set of connected clients.
Private clientSinks As New ArrayList()

' The client calls these methods to connect
' to, or detatch from, the event notification.
Public Sub Connect(ByVal sink As IEngineStatus)
clientSinks.Add(sink)
End Sub
Public Sub Disconnect(ByVal sink As IEngineStatus)
clientSinks.Remove(sink)
End Sub

End Class
5785ch10.qxd 3/31/06 10:51 AM Page 282
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 283
To actually send the events, let’s update the Car.Accelerate() method to iterate over the list of
sinks maintained by the ArrayList and send the correct notification when appropriate. Here is the
updated member in question:
' The Accelerate method now fires event notifications to the caller,
' rather than throwing a custom exception.
Public Sub Accelerate(ByVal delta As Integer)
' If the car is doomed, sent out event to
' each connected client.
If carIsDead Then
For Each i As IEngineStatus In clientSinks
i.Exploded("Sorry! This car is toast!")
Next
Else
currSpeed += delta
' Send out 'about to blow' event?
If (maxSpeed - currSpeed) = 10 Then
For Each i As IEngineStatus In clientSinks
i.AboutToBlow("Careful! About to blow!")

Next
End If
' Is the car doomed?
If currSpeed >= maxSpeed Then
carIsDead = True
Else
' We are OK, just print out speed.
Console.WriteLine("=> CurrSpeed = {0}", currSpeed)
End If
End If
End Sub
To complete the example, here is a Main() method making use of a callback interface to listen
to the Car events:
' Make a car and listen to the events.
Module Program
Sub Main()
Console.WriteLine("***** Interfaces as event enablers *****")
Dim myCar As New Car("SlugBug", 10)
' Make sink object.
Dim sink As New CarEventSink("MySink")
' Register the sink with the Car.
myCar.Connect(sink)
' Speed up (this will trigger the event notifications).
For i As Integer = 0 To 5
myCar.Accelerate(20)
Next
' Detach from event source.
myCar.Disconnect(sink)
Console.ReadLine()
End Sub

End Module
5785ch10.qxd 3/31/06 10:51 AM Page 283
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS284
Figure 10-1. Interfaces as event protocols
Figure 10-1 shows the end result of this interface-based event protocol.
Notice that we call Disconnect() before exiting Main(), although this is not actually necessary
for the example to function as intended. However, the Disconnect() method can be very helpful in
that it allows the caller to selectively detach from an event source at will. Assume that the application
now wishes to register two sink objects, dynamically remove a particular sink during the flow of exe-
cution, and continue processing the program at large:
Module Program
Sub Main()
Console.WriteLine("***** Interfaces as event enablers *****")
Dim myCar As New Car("SlugBug", 10)
' Make sink object.
Console.WriteLine("***** Creating Sinks! *****")
Dim sink As New CarEventSink("First Sink")
Dim otherSink As New CarEventSink("Second Sink")
' Pass both sinks to car.
myCar.Connect(sink)
myCar.Connect(otherSink)
' Speed up (this will trigger the events).
For i As Integer = 0 To 5
myCar.Accelerate(20)
Next
' Detach from first sink.
myCar.Disconnect(sink)
' Speed up again (only otherSink will be called).
For i As Integer = 0 To 5
myCar.Accelerate(20)

Next
' Detach from other sink.
myCar.Disconnect(otherSink)
Console.ReadLine()
End Sub
End Module
Figure 10-2 shows the update.
5785ch10.qxd 3/31/06 10:51 AM Page 284
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 285
Figure 10-2. Working with multiple sinks
So! Hopefully you agree that event interfaces can be helpful in that they can be used under any
language (VB 6.0, VB 2005, C++, etc.) or platform (COM, .NET, or J2EE) that supports interface-based
programming. However, as you may be suspecting, the .NET platform defines an “official” event
protocol that is not dependent on the construction of interfaces. To understand .NET’s intrinsic event
architecture, we begin by examining the role of the delegate type.
■Source Code The EventInterface project is located under the Chapter 10 subdirectory.
Understanding the .NET Delegate Type
Before formally defining .NET delegates, let’s gain a bit of historical perspective regarding the Windows
platform. Since its inception many years ago, the Win32 API made use of C-style function pointers
to support callback functionality. Using these function pointers, programmers were able to configure
one function in the program to invoke another function in the application. As you would imagine,
this approach allowed applications to handle events from various UI elements, intercept messages in
a distributed system, and numerous other techniques. Although BASIC-style languages have histor-
ically avoided the complexity of working with function pointers (thankfully), the callback construct
is burned deep into the fabric of the Windows API.
One of the problems found with C-style callback functions is that they represent little more than
a raw address in memory, which offers little by way of type safety or object orientation. Ideally, callback
functions could be configured to include additional type-safe information such as the number of
(and types of) parameters and the return value (if any) of the method being “pointed to.” Alas, this is
not the case in traditional callback functions, and, as you may suspect, can therefore be a frequent

source of bugs, hard crashes, and other runtime disasters.
Nevertheless, callbacks are useful entities in that they can be used to build event architectures.
In the .NET Framework, callbacks are still possible, and their functionality is accomplished in a much
safer and more object-oriented manner using delegates. In essence, a delegate is a type-safe object
that points to another method (or possibly multiple methods) in the application, which can be
invoked at a later time. Specifically speaking, a delegate type maintains three important pieces of
information:
• The address of the method on which it will make calls
• The arguments (if any) required by this method
• The return value (if any) returned from this method
5785ch10.qxd 3/31/06 10:51 AM Page 285
Figure 10-3. The BinaryOp delegate under the hood
Once a delegate has been defined and provided the necessary information, you may dynamically
invoke the method(s) it points to at runtime. As you will see, every delegate in the .NET Framework
(including your custom delegates) is automatically endowed with the ability to call their methods
synchronously (using the calling thread) or asynchronously (on a secondary thread in a nonblocking
manner). This fact greatly simplifies programming tasks, given that we can call a method on a sec-
ondary thread of execution without manually creating and managing a Thread object. This chapter will
focus on the synchronous aspect of the delegate type. We will examine the asynchronous behavior
of delegate types during our investigation of the System.Threading namespace in Chapter 16.
Defining a Delegate in VB 2005
When you want to create a delegate in VB 2005, you make use of the Delegate keyword. The name
of your delegate can be whatever you desire. However, you must define the delegate to match the
signature of the method it will point to. For example, assume you wish to build a delegate named
BinaryOp that can point to any method that returns an Integer and takes two Integers as input
parameters:
' This delegate can point to any method,
' taking two Integers and returning an
' Integer.
Public Delegate Function BinaryOp(ByVal x as Integer, _

ByVal y as Integer) As Integer
When the VB 2005 compiler processes a delegate type, it automatically generates a sealed class
deriving from System.MulticastDelegate. This class (in conjunction with its base class, System.
Delegate) provides the necessary infrastructure for the delegate to hold onto the list of methods to
be invoked at a later time. For example, if you examine the BinaryOp delegate using ildasm.exe, you
would find the autogenerated class type depicted in Figure 10-3.
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS286
As you can see, the generated BinaryOp class defines three public methods. Invoke() is perhaps
the core method, as it is used to invoke each method maintained by the delegate type in a synchronous
manner, meaning the caller must wait for the call to complete before continuing on its way. Strangely
enough, the synchronous Invoke() method is typically not directly called in code. As you will see in
just a bit, Invoke() is called behind the scenes when you make use of the appropriate VB 2005 syntax.
5785ch10.qxd 3/31/06 10:51 AM Page 286
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 287
BeginInvoke() and EndInvoke() provide the ability to call the method pointed to by the delegate
asynchronously on a second thread of execution. If you have a background in multithreading, you
are aware that one of the most common reasons developers create secondary threads of execution
is to invoke methods that require a good deal of time to complete. Although the .NET base class
libraries provide an entire namespace devoted to multithreaded programming (System.Threading),
delegates provide this functionality out of the box.
Investigating the Autogenerated Class Type
So, how exactly does the compiler know how to define the Invoke(), BeginInvoke(), and EndInvoke()
methods? To understand the process, here is the crux of the generated BinaryOp class type, shown in
dazzling pseudo-code:
' This is only pseudo-code!
NotInheritable Class BinaryOp
Inherits System.MulticastDelegate
' Compiler generated constructor.
Public Sub New(ByVal target As Object, ByVal functionAddress As System.UInt32)
End Sub

' Used for synchronous calls.
Public Sub Invoke(ByVal x As Integer, ByVal y As Integer)
End Sub
' Used for asynchronous calls on a second thread.
Public Function BeginInvoke(ByVal x As Integer, ByVal y As Integer, _
ByVal cb As AsyncCallback, ByVal state As Object) As IAsyncResult
End Function
Public Function EndInvoke(ByVal result As IAsyncResult) As Integer
End Function
End Class
First, notice that the parameters and return value defined for the Invoke() method exactly
match the definition of the BinaryOp delegate. The initial parameters to BeginInvoke() members
(two Integers in our case) are also based on the BinaryOp delegate; however, BeginInvoke() will
always provide two final parameters (of type AsyncCallback and Object) that are used to facilitate
asynchronous method invocations. Finally, the return value of EndInvoke() is identical to the original
delegate declaration and will always take as a sole parameter an object implementing the IAsyncResult
interface.
Let’s see another example. Assume you have defined a delegate that can point to any method
returning a String and receiving three Boolean input parameters:
Public Delegate Function MyDelegate(ByVal a As Boolean, ByVal b As Boolean, _
ByVal c As Boolean) As String
This time, the autogenerated class breaks down as follows:
NotInheritable Class MyDelegate
Inherits System.MulticastDelegate
Public Sub New(ByVal target As Object, ByVal functionAddress As System.UInt32)
End Sub
Public Function Invoke(ByVal a As Boolean, ByVal b As Boolean, _
ByVal c As Boolean) As String
End Function
Public Function BeginInvoke(ByVal a As Boolean, ByVal b As Boolean, _

ByVal c As Boolean, ByVal cb As AsyncCallback, _
ByVal state As Object) As IAsyncResult
End Function
5785ch10.qxd 3/31/06 10:51 AM Page 287
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS288
Public Function EndInvoke(ByVal result As IAsyncResult) As String
End Function
End Class
Delegates can also “point to” methods that contain any number of ByRef parameters. For example,
assume the following delegate type definition:
Public Delegate Function MyOtherDelegate(ByRef a As Boolean, _
ByRef b As Boolean, ByVal c As Integer) As String
The signatures of the Invoke() and BeginInvoke() methods look as you would expect; however,
check out the EndInvoke() method, which now includes the set of all ByRef arguments defined by
the delegate type:
NotInheritable Class MyOtherDelegate
Inherits System.MulticastDelegate
Public Sub New(ByVal target As Object, ByVal functionAddress As System.UInt32)
End Sub
Public Function Invoke(ByRef a As Boolean, ByRef b As Boolean, _
ByVal c As Integer) As String
End Function
Public Function BeginInvoke(ByRef a As Boolean, ByRef b As Boolean, _
ByVal c As Integer, ByVal cb As AsyncCallback, _
ByVal state As Object) As IAsyncResult
End Function
Public Function EndInvoke(ByRef a As Boolean, ByRef b As Boolean, _
ByVal result As IAsyncResult) As String
End Function
End Class

To summarize the story thus far, a VB 2005 delegate definition results in a compiler-generated
sealed class containing three methods (as well as an internally called constructor) whose parameter
and return types are based on the delegate’s declaration. Again, the good news is that the VB 2005
compiler is the entity in charge of defining the actual delegate definition on our behalf.
The System.MulticastDelegate and System.Delegate
Base Classes
So, when you build a type using the VB 2005 Delegate keyword, you indirectly declare a class type
that derives from System.MulticastDelegate. This class provides descendents with access to a list
that contains the addresses of the methods maintained by the delegate type, as well as several addi-
tional methods to interact with the invocation list. MulticastDelegate obtains additional functionality
from its parent class, System.Delegate.
Now, do understand that you will never directly derive from these base classes (in fact it is a com-
plier error to do so). However, all delegate types inherit the members documented in Table 10-1
(consult the .NET Framework 2.0 documentation for full details).
5785ch10.qxd 3/31/06 10:51 AM Page 288
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 289
Table 10-1. Select Members of System.MultcastDelegate/System.Delegate
Inherited Member Meaning in Life
Method This property returns a System.Reflection.MethodInfo type that
represents details of a shared method that is maintained by the delegate.
Target If the method to be called is defined at the object level (rather than
a shared method), Target returns the name of the method maintained by
the delegate. If the value returned from Target equals Nothing, the
method to be called is a shared member.
Combine() This shared method adds a method to the list maintained by the delegate.
GetInvocationList() This method returns an array of System.Delegate types, each representing
a particular method maintained by the delegate’s invocation list.
Remove() These shared methods removes a method (or all methods) from the
RemoveAll() invocation list.
The Simplest Possible Delegate Example

Delegates tend to cause a great deal of confusion when encountered for the first time (even for
those who do have experience with C-style callback functions). Thus, to get the ball rolling, let’s take
a look at a very simple console application (named SimpleDelegate) that makes use of our BinaryOp
delegate type. Here is the complete code (defined within a single *.vb file), with analysis to follow:
' Our delegate type can point to any method
' taking two integers and returning an integer.
Public Delegate Function BinaryOp(ByVal x As Integer, _
ByVal y As Integer) As Integer
' This class defines the methods that will be 'pointed to' by the delegate.
Public Class SimpleMath
Public Shared Function Add(ByVal x As Integer, ByVal y As Integer) As Integer
Return x + y
End Function
Public Shared Function Subtract(ByVal x As Integer, _
ByVal y As Integer) As Integer
Return x - y
End Function
End Class
Module Program
Sub Main()
Console.WriteLine("***** Simple Delegate Example *****")
' Make a delegate object and add method to invocation
' list using the AddressOf keyword.
Dim b As BinaryOp = New BinaryOp(AddressOf SimpleMath.Add)
' Invoke the method 'pointed to'
Console.WriteLine("10 + 10 is {0}", b(10, 10))
Console.ReadLine()
End Sub
End Module
5785ch10.qxd 3/31/06 10:51 AM Page 289

CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS290
Again notice the format of the BinaryOp delegate, which can point to any method taking two
Integers and returning an Integer. Given this, we have created a class named SimpleMath, which
defines two shared methods that (surprise, surprise) match the pattern defined by the BinaryOp
delegate. When you want to insert the target method to a given delegate, simply pass in the name
of the method to the delegate’s constructor using the VB 2005 AddressOf keyword.
At this point, you are able to invoke the member pointed to using a syntax that looks like
a direct method invocation:
' Invoke() is really called here!
Console.WriteLine("10 + 10 is {0}", b(10, 10))
Under the hood, the runtime actually calls the compiler-generated Invoke() method. You can
verify this fact for yourself if you open your assembly in ildasm.exe and investigate the CIL code
within the Main() method. Here is a partial code snippet:
.method private hidebysig static void Main(string[] args) cil managed
{

callvirt instance int32 SimpleDelegate.BinaryOp::Invoke(int32, int32)

}
If you wish to call the Invoke() method directly, you are free to do so:
' Call Invoke() directly.
Console.WriteLine("10 + 10 is {0}", b.Invoke(10, 10))
Recall that .NET delegates are intrinsically type safe. Therefore, if you attempt to pass a delegate
the address of a method that does not “match the pattern,” you receive a compile-time error. To
illustrate, assume the SimpleMath class now defines an additional method named SquareNumber() as
follows:
Public Class SimpleMath

Public Shared Function SquareNumber(ByVal a As Integer) As Integer
Return a * a

End Function
End Class
Given that the BinaryOp delegate can only point to methods that take two Integers and return
an Integer, the following code is illegal and will not compile:
' Error! Method does not match delegate pattern!
Dim b As New BinaryOp(AddressOf SimpleMath.SquareNumber)
Interacting with a Delegate Object
Let’s spice up the current example by defining a helper function within our module named
DisplayDelegateInfo(). This method will print out names of the methods maintained by the
incoming delegate type as well as the name of the class defining the method. To do so, we will iterate
over the System.Delegate array returned by GetInvocationList(), invoking each object’s Target
and Method properties:
Sub DisplayDelegateInfo(ByVal delObj As System.Delegate)
For Each d As System.Delegate In delObj.GetInvocationList()
Console.WriteLine("Method Name: {0}", d.Method)
Console.WriteLine("Type Name: {0}", d.Target)
Next
End Sub
5785ch10.qxd 3/31/06 10:51 AM Page 290
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 291
Figure 10-4. Investigation our BinaryOp delegate
Figure 10-5. “Pointing to” instance-level methods
Assuming you have updated your Main() method to actually call this new helper method by
passing in your BinaryOp object:
Sub Main()

Dim b As BinaryOp = New BinaryOp(AddressOf SimpleMath.Add)
' Invoke the method 'pointed to' as before.
Console.WriteLine("10 + 10 is {0}", b(10, 10))
DisplayDelegateInfo(b)


End Sub
you would find the output shown in Figure 10-4.
Notice that the name of the type (SimpleMath) is currently not displayed by the Target property.
The reason has to do with the fact that our BinaryOp delegate is pointing to shared methods and
therefore there is no object to reference! However, if we update the Add() and Subtract methods to
be instance-level members (simply by deleting the Shared keywords), we could create an instance
of the SimpleMath type and specify the methods to invoke as follows:
Sub Main()
Console.WriteLine("***** Simple Delegate Example *****")
' Make a new SimpleMath object.
Dim myMath As New SimpleMath()
' Use this object to specify the address of the Add method.
Dim b As BinaryOp = New BinaryOp(AddressOf myMath.Add)
' Invoke the method 'pointed to' as before.
Console.WriteLine("10 + 10 is {0}", b(10, 10))
DisplayDelegateInfo(b)
Console.ReadLine()
End Sub
In this case, we would find the output shown in Figure 10-5.
5785ch10.qxd 3/31/06 10:51 AM Page 291
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS292
■Source Code The SimpleDelegate project is located under the Chapter 10 subdirectory.
Retrofitting the Car Type with Delegates
Clearly, the previous SimpleDelegate example was intended to be purely illustrative in nature, given
that there would be no compelling reason to build a delegate simply to add two numbers. Hopefully,
however, this example demystifies the basic process of working with delegate types.
To provide a more realistic use of delegate types, let’s retrofit our Car type to send the Exploded
and AboutToBlow notifications using .NET delegates rather than a custom event interface. Beyond
no longer implementing IEngineStatus, here are the steps we will take:

• Define the AboutToBlow and Exploded delegates.
• Declare member variables of each delegate type in the Car class.
• Create helper functions on the Car that allow the caller to specify the methods to add to the
delegate member variable’s invocation lists.
• Update the Accelerate() method to invoke the delegate’s invocation list under the correct
circumstances.
First, consider the following updates to the Car class, which address the first three points:
Public Class Car
' Our delegate types are nested in the Car type.
Public Delegate Sub AboutToBlow(ByVal msg As String)
Public Delegate Sub Exploded(ByVal msg As String)
' Because delegates are simply classes, we can create
' member variables of delegate types.
Private almostDeadList As AboutToBlow
Private explodedList As Exploded
' To allow the caller to pass us a delegate object.
Public Sub OnAboutToBlow(ByVal clientMethod As AboutToBlow)
almostDeadList = clientMethod
End Sub
Public Sub OnExploded(ByVal clientMethod As Exploded)
explodedList = clientMethod
End Sub

End Class
Notice in this example that we define the delegate types directly within the scope of the Car type.
From a design point of view, it is quite natural to define a delegate within the scope of the type it
naturally works with given that it illustrates a tight association between the two types. Furthermore,
given that the compiler transforms a delegate into a full class definition, what we have actually done
is indirectly created two nested classes.
Next, note that we declare two member variables (one for each delegate type) and two helper

functions (OnAboutToBlow() and OnExploded()) that allow the client to add a method to the delegate’s
invocation list. In concept, these methods are similar to the Connect() and Disconnect() methods we
created during the EventInterface example. Of course, in this case, the incoming parameter is a client-
allocated delegate object rather than a sink implementing a specific event interface.
At this point, we need to update the Accelerate() method to invoke each delegate, rather than
iterate over an ArrayList of client-supplied sinks:
5785ch10.qxd 3/31/06 10:51 AM Page 292
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 293
Public Sub Accelerate(ByVal delta As Integer)
If carIsDead Then
' If the car is doomed, send out the Exploded notification.
If Not (explodedList Is Nothing) Then
explodedList("Sorry, this car is dead ")
End If
Else
currSpeed += delta
' Are we almost doomed? If so, send out AboutToBlow notification.
If 10 = maxSpeed - currSpeed AndAlso Not (almostDeadList Is Nothing) Then
almostDeadList("Careful buddy! Gonna blow!")
End If
If currSpeed >= maxSpeed Then
carIsDead = True
Else
Console.WriteLine("->CurrSpeed = {0}", currSpeed)
End If
End If
End Sub
Notice that before we invoke the methods maintained by the almostDeadList and explodedList
member variables, we are checking them against the value Nothing. The reason is that it will be the
job of the caller to allocate these objects when calling the OnAboutToBlow() and OnExploded() helper

methods. If the caller does not call these methods (given that it may not wish to hear about these
events), and we attempt to invoke the delegate’s invocation list, we will trigger a NullReferenceException
and bomb at runtime (which would obviously be a bad thing!).
Now that we have the delegate infrastructure in place, observe the updates to the Program module:
Module Program
Sub Main()
Console.WriteLine("***** Delegates as event enablers *****")
Dim c1 As Car = New Car("SlugBug", 10)
' Pass the address of the methods that will be maintained
' by the delegate member variables of the Car type.
c1.OnAboutToBlow(AddressOf CarAboutToBlow)
c1.OnExploded(AddressOf CarExploded)
Console.WriteLine("***** Speeding up *****")
For i As Integer = 0 To 5
c1.Accelerate(20)
Next
Console.ReadLine()
End Sub
' These are called by the Car object.
Public Sub CarAboutToBlow(ByVal msg As String)
Console.WriteLine(msg)
End Sub
Public Sub CarExploded(ByVal msg As String)
Console.WriteLine(msg)
End Sub
End Module
Notice that in this code example, we are not directly allocating an instance of the Car.AboutToBlow
or Car.Exploded delegate objects. However, when we make use of the VB 2005 AddressOf keyword, the
compiler will automatically generate a new instance of the related delegate type. This can be verified
using ildasm.exe (which I will leave as an exercise to the interested reader).

5785ch10.qxd 3/31/06 10:51 AM Page 293
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS294
While the fact that the AddressOf keyword automatically generates the delegate objects in the
background is quite helpful, there will be times when you will prefer to allocate the delegate object
manually for later use in your application. We will see a practical reason to do so in the next section;
however, to illustrate the process, consider the following iteration of Main():
Module Program
Sub Main()
Console.WriteLine("***** Delegates as event enablers *****")
Dim c1 As Car = New Car("SlugBug", 10)
' Manually create the delegate objects.
Dim aboutToBlowDel As New Car.AboutToBlow(AddressOf CarAboutToBlow)
Dim explodedDel As New Car.Exploded(AddressOf CarExploded)
' Now pass in delegate objects.
c1.OnAboutToBlow(aboutToBlowDel)
c1.OnExploded(explodedDel)

End Sub
Public Sub CarAboutToBlow(ByVal msg As String)
Console.WriteLine(msg)
End Sub
Public Sub CarExploded(ByVal msg As String)
Console.WriteLine(msg)
End Sub
End Module
The only major point to be made here is because of the fact that the AboutToBlow and Exploded dele-
gates are nested within the Car class, we must allocate them using their full name (e.g., Car.AboutToBlow).
Like any delegate constructor, we pass in the name of the method to add to the invocation list.
Enabling Multicasting
Recall that .NET delegates have the intrinsic ability to multicast. In other words, a delegate object

can maintain a list of methods to call (provided they match the pattern defined by the delegate),
rather than a single method. When you wish to add multiple methods to a delegate object, you will
need to call System.Delegate.Combine(). To enable multicasting on the Car type, we could update
the OnAboutToBlow() and OnExploded() methods as follows:
Class Car

' Now with multicasting!
Public Sub OnAboutToBlow(ByVal clientMethod As AboutToBlow)
almostDeadList = System.Delegate.Combine(almostDeadList, clientMethod)
End Sub
Public Sub OnExploded(ByVal clientMethod As Exploded)
explodedList = System.Delegate.Combine(explodedList, clientMethod)
End Sub

End Class
Be aware that the previous code will only compile if Option Strict is not enabled in your proj-
ect. Since this is always good practice, here would be a more type-safe (and compiler acceptable)
implementation of these methods using explicit casting:
' Now with type-safe multicasting!
Public Sub OnAboutToBlow(ByVal clientMethod As AboutToBlow)
5785ch10.qxd 3/31/06 10:51 AM Page 294
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 295
almostDeadList = CType(System.Delegate.Combine(almostDeadList, _
clientMethod), AboutToBlow)
End Sub
Public Sub OnExploded(ByVal clientMethod As Exploded)
explodedList = CType(System.Delegate.Combine(explodedList, _
clientMethod), Exploded)
End Sub
In either case, the first argument to pass into Combine() is the delegate object that is maintain-

ing the current invocation list, while the second argument is the new delegate object you wish to
add to the list. At this point, the caller can now register multiple targets as follows:
Module Program
Sub Main()
Console.WriteLine("***** Delegates as event enablers *****")
Dim c1 As Car = New Car("SlugBug", 10)
' Register multiple event handlers!
c1.OnAboutToBlow(AddressOf CarAboutToBlow)
c1.OnAboutToBlow(AddressOf CarIsAlmostDoomed)
c1.OnExploded(AddressOf CarExploded)

End Sub
' This time, two methods are called
' when the AboutToBlow notification fires.
Public Sub CarAboutToBlow(ByVal msg As String)
Console.WriteLine(msg)
End Sub
Public Sub CarIsAlmostDoomed(ByVal msg As String)
Console.WriteLine("Critical Message from Car: {0}", msg)
End Sub
Public Sub CarExploded(ByVal msg As String)
Console.WriteLine(msg)
End Sub
End Module
Removing a Target from a Delegate’s Invocation List
The Delegate class also defines a shared Remove() method that allows a caller to dynamically
remove a member from the invocation list. If you wish to allow the caller the option to detach from
the AboutToBlow and Exploded notifications, you could add the following additional helper methods
to the Car type:
Class Car


' To remove a target from the list.
Public Sub RemoveAboutToBlow(ByVal clientMethod As AboutToBlow)
almostDeadList = CType(System.Delegate.Remove(almostDeadList, _
clientMethod), AboutToBlow)
End Sub
Public Sub RemoveExploded(ByVal clientMethod As Exploded)
explodedList = CType(System.Delegate.Remove(explodedList, _
clientMethod), Exploded)
End Sub

End Class
5785ch10.qxd 3/31/06 10:51 AM Page 295
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS296
Figure 10-6. The Car type, now with delegates
Thus, we could stop receiving the Exploded notification by updating Main() as follows:
Sub Main()
Console.WriteLine("***** Delegates as event enablers *****")
Dim c1 As Car = New Car("SlugBug", 10)
' Register multiple event handlers!
c1.OnAboutToBlow(AddressOf CarAboutToBlow)
c1.OnAboutToBlow(AddressOf CarIsAlmostDoomed)
c1.OnExploded(AddressOf CarExploded)
Console.WriteLine("***** Speeding up *****")
For i As Integer = 0 To 5
c1.Accelerate(20)
Next
' Remove CarExploded from invocation list.
c1.RemoveExploded(AddressOf CarExploded)
' This will not fire the Exploded event.

For i As Integer = 0 To 5
c1.Accelerate(20)
Next
Console.ReadLine()
End Sub
The final output of our CarDelegate application can be seen in Figure 10-6.
■Source Code The CarDelegate project is located under the Chapter 10 subdirectory.
Understanding (and Using) Events
Delegates are fairly interesting constructs in that they enable two objects in memory to engage in
a two-way conversation in a type-safe and object-oriented manner. As you may agree, however,
working with delegates in the raw does entail a good amount of boilerplate code (defining the dele-
gate, declaring any necessary member variables, and creating custom registration/unregistration
methods).
5785ch10.qxd 3/31/06 10:51 AM Page 296
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 297
Because the ability for one object to call back to another object is such a helpful construct, VB 2005
provides a small set of keywords to lessen the burden of using delegates in the raw. For example, when
the compiler processes the Event keyword, you are automatically provided with registration and
unregistration methods that allow the caller to hook into an event notification. Better yet, using the
Event keyword removes the need to define delegate objects in the first place. In this light, the Event
keyword is little more than syntactic sugar, which can be used to save you some typing time.
To illustrate these new keywords, let’s reconfigure the Car class to make use of VB 2005 events,
rather than raw delegates. First, you need to define the events themselves using the VB .NET Event
keyword. Notice that events are defined with regards to the set of parameters passed into the regis-
tered handler:
Public Class Car

' This car can send these events.
Public Event Exploded(ByVal msg As String)
Public Event AboutToBlow(ByVal msg As String)


End Class
Firing an Event Using the RaiseEvent Keyword
Firing an event is as simple as specifying the event by name (with any specified parameters) using
the RaiseEvent keyword. To illustrate, update the previous implementation of Accelerate() to send
each event accordingly:
Public Sub Accelerate(ByVal delta As Integer)
If carIsDead Then
' If the car is doomed, raise Exploded event.
RaiseEvent Exploded("Sorry, this car is dead ")
Else
currSpeed += delta
' Are we almost doomed? If so, send out AboutToBlow event.
If 10 = maxSpeed - currSpeed Then
RaiseEvent AboutToBlow("Careful buddy! Gonna blow!")
End If
If currSpeed >= maxSpeed Then
carIsDead = True
Else
Console.WriteLine("->CurrSpeed = {0}", currSpeed)
End If
End If
End Sub
With this, you have configured the car to send two custom events (under the correct conditions).
You will see the usage of this new automobile in just a moment, but first, let’s dig a bit deeper into
the VB 2005 Event keyword.
Events Under the Hood
A VB 2005 event actually encapsulates a good deal of information. Each time you declare an event
with the Event keyword, the compiler generates the following information within the defining class:
5785ch10.qxd 3/31/06 10:51 AM Page 297

CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS298
• A new hidden, nested delegate is created automatically and added to your class. The name of
this delegate is always EventName+EventHandler. For example, if you have an event named
Exploded, the autogenerated delegate is named ExplodedEventHandler.
• Two hidden public functions, one having an “add_” prefix, the other having a “remove_” prefix,
are automatically added to your class. These are used internally to call Delegate.Combine()
and Delegate.Remove(), in order to add and remove methods to/from the list maintained by
the delegate.
• A new hidden member variable is added to your class that represents a new instance of the
autogenerated delegate type (see the first bullet item).
As you can see, the Event keyword is indeed a timesaver as it instructs the compiler to author
the same sort of code you created manually when using the delegate type directly!
If you were to compile the CarEvent example and load the assembly into ildasm.exe, you could
check out the CIL instructions behind the compiler-generated add_AboutToBlow(). Notice it calls
Delegate.Combine() on your behalf. Also notice that the parameter passed to add_AboutToBlow() is
an instance of the autogenerated AboutToBlowEventHandler delegate:
.method public specialname instance void
add_AboutToBlow(class CarEvent.Car/AboutToBlowEventHandler obj)
cil managed synchronized
{

IL_0008: call class [mscorlib]System.Delegate
[mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)

} // end of method Car::add_AboutToBlow
Furthermore, remove_AboutToBlow() makes the call to Delegate.Remove() automatically, passing
in the incoming AboutToBlowEventHandler delegate:
.method public specialname instance void
remove_AboutToBlow(class CarEvent.Car/AboutToBlowEventHandler obj)

cil managed synchronized
{

IL_0008: call class [mscorlib]System.Delegate
[mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)

} // end of method Car::remove_AboutToBlow
The CIL instructions for the event declaration itself makes use of the .addon and .removeon CIL
tokens to connect the correct add_XXX() and remove_XXX() methods:
.event CarEvents.Car/EngineHandler AboutToBlow
{
.addon
void CarEvents.Car::add_AboutToBlow(class CarEvents.Car/EngineHandler)
.removeon
void CarEvents.Car::remove_AboutToBlow(class CarEvents.Car/EngineHandler)
} // end of event Car::AboutToBlow
Perhaps most important, if you check out the CIL behind this iteration of the Accelerate()
method, you find that the delegate is invoked on your behalf. Here is a partial snapshot of the CIL
that invokes the invocation list maintained by the ExplodedEventHandler delegate:
5785ch10.qxd 3/31/06 10:51 AM Page 298
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 299
.method public instance
void Accelerate(int32 delta) cil managed
{

IL_001d: callvirt
instance void CarEvents.Car/ExplodedEventHandler::Invoke(string)

}

As you can see, the VB 2005 Event keyword is quite helpful, given that it builds and manipulates
raw delegates on your behalf. As you saw earlier in this chapter, however, you are able to directly
manipulate delegates if you so choose.
Hooking into Incoming Events Using WithEvents and Handles
Now that you understand how to build a class that can send events, the next big question is how you
can configure an object to receive these events. Assume you have now created an instance of the Car
class and want to listen to the events it is capable of sending.
The first step is to declare a member variable for which you wish to process incoming events
using the WithEvents keyword. Next, you will associate an event to a particular event handler using
the Handles keyword. For example:
Module Program
' Declare member variables 'WithEvents' to
' capture the events.
Dim WithEvents c As New Car("NightRider", 50)
Sub Main()
Console.WriteLine("***** Fun with Events *****")
Dim i As Integer
For i = 0 To 5
c.Accelerate(10)
Next
End Sub
' Event Handlers.
Public Sub MyExplodedHandler(ByVal s As String) _
Handles c.Exploded
Console.WriteLine(s)
End Sub
Public Sub MyAboutToDieHandler(ByVal s As String) _
Handles c.AboutToBlow
Console.WriteLine(s)
End Sub

End Module
In many ways, things look more or less like traditional VB 6.0 event logic. The only new spin is
the fact that the Handles keyword is now used to connect the handler to an object’s event.
■Note As you may know, VB 6.0 demanded that event handlers always be named using very strict naming
conventions (
NameOfTheObject_NameOfTheEvent) that could easily break as you renamed the objects in your
code base. With the VB 2005 Handles keyword, however, the name of your event handlers can be anything you choose.
5785ch10.qxd 3/31/06 10:51 AM Page 299
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS300
Multicasting Using the Handles Keyword
Another extremely useful aspect of the Handles statement is the fact that you are able to configure
multiple methods to process the same event. For example, if you update your module as follows:
Module Program

Public Sub MyExplodedHandler(ByVal s As String) _
Handles c.Exploded
Console.WriteLine(s)
End Sub
' Both of these handlers will be called when AboutToBlow is fired.
Public Sub MyAboutToDieHandler(ByVal s As String) _
Handles c.AboutToBlow
Console.WriteLine(s)
End Sub
Public Sub MyAboutToDieHandler2(ByVal s As String) _
Handles c.AboutToBlow
Console.WriteLine(s)
End Sub
End Module
you would see the incoming String object sent by the AboutToBlow event print out twice, as we have
handled this event using two different event handlers.

Defining a Single Handler for Multiple Events
The Handles keyword also allows you to define a single handler to (pardon the redundancy) handle
multiple events, provided that the events are passing in the same set of arguments. This should make
sense, as the VB 2005 Event keyword is simply a shorthand notation for working with type-safe delegates.
In our example, given that the Exploded and AboutToBlow events are both passing a single string by
value, we could intercept each event using the following handler:
Module Program
Dim WithEvents c As New Car("NightRider", 50)
Sub Main()
Console.WriteLine("***** Fun with Events *****")
Dim i As Integer
For i = 0 To 5
c.Accelerate(10)
Next
End Sub
' A single handler for each event.
Public Sub MyExplodedHandler(ByVal s As String) _
Handles c.Exploded, c.AboutToBlow
Console.WriteLine(s)
End Sub
End Module
■Source Code The CarEvents project is located under the Chapter 10 subdirectory.
5785ch10.qxd 3/31/06 10:51 AM Page 300
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 301
Dynamically Hooking into Incoming Events with AddHandler/
RemoveHandler
Currently, we have been hooking into an event by explicitly declaring the variable using the WithEvents
keyword. When you do so, you make a few assumptions in your code:
• The variable is not a local variable, but a member variable of the defining type (Module, Class,
or Structure).

• You wish to be informed of the event throughout the lifetime of your application.
Given these points, it is not possible to declare a local variable using the WithEvents keyword:
Sub Main()
' Error! Local variables cannot be
' declared 'with events'.
Dim WithEvents myCar As New Car()
End Sub
However, under the .NET platform, you do have an alternative method that may be used to
hook into an event. It is possible to declare a local object (as well as a member variable of a type)
without using the WithEvents keyword, and dynamically rig together an event handler at runtime.
To do so, you ultimately need to call the correct autogenerated add_XXX() method to ensure
that your method is added to the list of function pointers maintained by the Car’s internal delegate
(remember, the Event keyword expands to produce—among other things—a delegate type). Of course,
you do not call add_XXX() directly, but rather use the VB 2005 AddHandler statement.
As well, if you wish to dynamically remove an event handler from the underlying delegate’s invoca-
tion list, you can indirectly call the compiler-generated remove_XXX() method using the RemoveHandler
statement.
In a nutshell, AddHandler/RemoveHandler allows us to gain “delegate-like” functionality without
directly interacting defining the delegate types. Consider the following reworked Main() method:
Module Program
Sub Main()
Console.WriteLine("***** Fun with AddHandler/RemoveHandler *****")
' Note lack of WithEvents keyword.
Dim c As New Car("NightRider", 50)
' Dynamically hook into event using AddHandler.
AddHandler c.Exploded, AddressOf CarEventHandler
AddHandler c.AboutToBlow, AddressOf CarEventHandler
For i As Integer = 0 To 5
c.Accelerate(10)
Next

Console.ReadLine()
End Sub
' Event Handler for both events
' (note lack of Handles keyword).
Public Sub CarEventHandler(ByVal s As String)
Console.WriteLine(s)
End Sub
End Module
5785ch10.qxd 3/31/06 10:51 AM Page 301
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS302
As you can see, the AddHandler statement requires the name of the event you want to listen to,
and the address of the method that will be invoked when the event is sent. Here, you also routed each
event to a single handler (which is of course not required). As well, if you wish to enable multicasting,
simple use the AddHandler statement multiple times and specify unique targets:
' Multicasting!
AddHandler c.Exploded, AddressOf MyExplodedHandler
AddHandler c. Exploded, AddressOf MySecondExplodedHandler
RemoveHandler works in the same manner. If you wish to stop receiving events from a particular
object, you may do so using the following syntax:
' Dynamically unhook a handler using RemoveHandler.
RemoveHandler c. Exploded, AddressOf MySecondExplodedHandler
At this point you may wonder when (or if) you would ever need to make use of the AddHandler
and RemoveHandler statements, given that VB 2005 supports the WithEvents syntax. Again, understand
that this approach is very powerful, given that you have the ability to detach from an event source
at will.
When you make use of the WithEvent keyword, you will continuously receive events from the
source object until the object dies (which typically means until the client application is terminated).
Using the RemoveHandler statements, you can simply tell the object “Stop sending me this event,”
even though the object may be alive and well in memory.
■Source Code The DynamicCarEvents project is located under the Chapter 10 subdirectory.

Defining a “Prim-and-Proper” Event
Truth be told, there is one final enhancement we could make to our CarEvents examples that mirrors
Microsoft’s recommended event pattern. As you begin to explore the events sent by a given type in
the base class libraries, you will find that the target method’s first parameter is a System.Object, while
the second parameter is a type deriving from System.EventArgs.
The System.Object argument represents a reference to the object that sent the event (such as
the Car), while the second parameter represents information regarding the event at hand. The
System.EventArgs base class represents an event that is not sending any custom information:
Public Class EventArgs
Public Shared ReadOnly Empty As EventArgs
Shared Sub New()
End Sub
Public Sub New()
End Sub
End Class
Our current examples have specified the parameters they send directly within the definition of
the event itself:
Public Class Car

' Notice we are specifying the event arguments directly.
Public Event Exploded(ByVal msg As String)
Public Event AboutToBlow(ByVal msg As String)

End Class
5785ch10.qxd 3/31/06 10:51 AM Page 302
CHAPTER 10 ■ CALLBACK INTERFACES, DELEGATES, AND EVENTS 303
As you have learned, the compiler will take these arguments to define a proper delegate behind
the scenes. While this approach is very straightforward, if you do wish to follow the recommended
design pattern, the Exploded and AboutToBlow events should be retrofitted to send a System.Object
and System.EventArgs descendent.

Although you can pass an instance of EventArgs directly, you lose the ability to pass in custom
information to the registered event handler. Thus, when you wish to pass along custom data, you
should build a suitable class deriving from EventArgs. For our example, assume we have a class named
CarEventArgs, which maintains a string representing the message sent to the receiver:
Public Class CarEventArgs
Inherits EventArgs
Public ReadOnly msgData As String
Public Sub New(ByVal msg As String)
msgData = msg
End Sub
End Class
With this, we would now update the events sent from the Car type like so:
Public Class Car

' These events follow Microsoft design guidelines.
Public Event Exploded(ByVal sender As Object, ByVal e As CarEventArgs)
Public Event AboutToBlow(ByVal sender As Object, ByVal e As CarEventArgs)

End Class
When firing our events from within the Accelerate() method, we would now need to supply
a reference to the current Car (via the Me keyword) and an instance of our CarEventArgs type:
Public Sub Accelerate(ByVal delta As Integer)
If carIsDead Then
' If the car is doomed, send out the Exploded notification.
RaiseEvent Exploded(Me, New CarEventArgs("This car is doomed "))
Else
currSpeed += delta
' Are we almost doomed? If so, send out AboutToBlow notification.
If 10 = maxSpeed - currSpeed Then
RaiseEvent AboutToBlow(Me, New CarEventArgs("Slow down!"))

End If
If currSpeed >= maxSpeed Then
carIsDead = True
Else
Console.WriteLine("->CurrSpeed = {0}", currSpeed)
End If
End If
End Sub
On the caller’s side, all we would need to do is update our event handlers to receive the incoming
parameters and obtain the message via our read-only field. For example:
' Assume this event was handled using AddHandler.
Public Sub AboutToBlowHandler(ByVal sender As Object, ByVal e As CarEventArgs)
Console.WriteLine("{0} says: {1}", sender, e.msgData)
End Sub
5785ch10.qxd 3/31/06 10:51 AM Page 303

×