So far, you have seen how to create and manipulate your objects, working with the result of operations performed onto object instances or shared members. All the work up to now does not provide a way for understanding the moment when a particular thing happens or for being notified of a happening. In .NET development, as with many other programming environments, getting notifications for occurrences and knowing the moment when something happens is accomplished by handling events. Events are information that an object sends to the caller, such as the user interface or a thread, about its state so that the caller can make decisions according to the occurred event. Events in .NET programming are powerful and provide great granularity about controlling each happening. This granularity can take place because of another feature named delegates, which provide the real infrastructure for event-based programming. Because of this, we discuss delegates first before going into the events discussion.
Delegates are type-safe function pointers. The main difference between classic function pointers (such as C++ pointers) and delegates is that function pointers can point anywhere, which can be dangerous. Delegates, on the other hand, can point only to those methods that respect delegates’ signatures. Delegates hold a reference to a procedure (its address) and enable applications to invoke different methods at runtime, but such methods must adhere to the delegate signature. Delegates also enable you to invoke a method from another object. This is important according to the main purpose of delegates, which is offering an infrastructure for handling events.
Delegates are used in event-handling architectures, but they are powerful and can be used in other advanced techniques. You find examples of delegates being used in multithreading or in LINQ expressions. In Chapter 20, “Advanced Language Features,” you learn about lambda expressions that enable implementing delegates on-the-fly.
Delegates are reference types deriving from System.Delegate
. Because of this, they can also be defined at namespace level, as you might recall from Chapter 9, “Organizing Types Within Namespaces.” They can also be defined at the class and module level. In the next section, you learn to define delegates. Pay attention to this topic because delegates have a particular syntax for being defined and that can be a little bit confusing.
A delegate is defined via the Delegate
keyword followed by the method signature it needs to implement. Such a signature can be referred to as a Sub
or as a Function
and might or might not receive arguments. The following code defines a delegate that can handle a reference to methods able to check an email address, providing the same signature of the delegate:
Public Delegate Function IsValidMailAddress(ByVal emailAddress _
As String) As Boolean
Delegates Support Generics
Delegates support generics, meaning that you can define a Delegate Function (Of T)
or Delegate Sub(Of T)
.
Notice that IsValidMailAddress
is a type and not a method as the syntax might imply. emailAddress
is a variable containing the email address to check while any methods respecting the signature return a Boolean value depending on the check result. To use a delegate, you need to create an instance of it. The delegate’s constructor requires you to specify an AddressOf
clause pointing to the actual method that accomplishes the required job. After you get the instance, the delegate points to the desired method that you can call using the Invoke
method. The following code demonstrates the described steps:
'The method's signature is the same as the delegate: correct
Function CheckMailAddress(ByVal emailAddress As String) As Boolean
'Validates emails via regular expressions, according to the
'following pattern
Dim validateMail As String = "^([w-.]+)@(([[0-9]{1,3}." & _
"[0-9]{1,3}.)|(([w-]+.)+))([a-zA-z]{2,4}|[0-9]{1,3})(]?)$"
Return Text.RegularExpressions.Regex.IsMatch(emailAddress,
validateMail)
End Function
Sub Main()
'Creates an instance of the delegate and points to
'the specified method
Dim mailCheck As New IsValidMailAddress(AddressOf CheckMailAddress)
'You invoke a delegate via the Invoke method
Dim result As Boolean = mailCheck.
Invoke("[email protected]")
Console.WriteLine("Is valid: {0}", result)
End Sub
At this point, you might wonder why delegates are so interesting if they require so much code just for a simple invocation. The first reason is that you can provide as many methods as you like that respect the delegate signature and decide which of the available methods to invoke. For example, the following code provides an alternative (and much simplified) version of the CheckMailAddress
method, named CheckMailAddressBasic
:
Function CheckMailAddressBasic(ByVal emailAddress As String) As Boolean
Return emailAddress.Contains("@")
End Function
Although different, the method still respects the delegate signature. Now you can invoke the new method by changing the AddressOf
clause, without changing code that calls Invoke
for calling the method:
'Alternative syntax: if you already declared
'an instance, you can do this assignment
mailCheck = AddressOf CheckMailAddressBasic
'No changes here!
Dim result As Boolean = mailCheck.
Invoke("[email protected]")
Console.WriteLine("Is valid: {0}", result)
If you still wonder why all this can be useful, consider a scenario in which you have hundreds of invocations to the same method. Instead of replacing all the invocations, you can change what the delegate is pointing to. Invoke
is also the default member for the Delegate
class, so you can rewrite the previous invocation as follows:
Dim result As Boolean = mailCheck("[email protected]")
This also works against methods that do not require arguments.
Starting with Visual Basic 2008, the .NET Framework introduced new language features such as lambda expressions and relaxed delegates. Both are intended to work with delegates (and in the case of lambda expressions, to replace them in some circumstances), but because of their strict relationship with LINQ, they are discussed in Chapter 20, which is preparatory for the famous data access technology.
A delegate can hold a reference (that is, the address) to a method. It is possible to create delegates holding references to more than one method by creating multicast delegates. A multicast delegate is the combination of two or more delegates into a single delegate, providing the delegate the capability to make multiple invocations. The following code demonstrates how to create a multicast delegate, having two instances of the same delegate pointing to two different methods:
'The delegate is defined at namespace level
Public Delegate Sub WriteTextMessage(ByVal textMessage As String)
'....
Private textWriter As New WriteTextMessage(AddressOf WriteSomething)
Private complexWriter As New WriteTextMessage(AddressOf _
WriteSomethingMoreComplex)
Private Sub WriteSomething(ByVal text As String)
Console.WriteLine(" report your text: {0}", text)
End Sub
Private Sub WriteSomethingMoreComplex(ByVal text As String)
Console.WriteLine("Today is {0} and you wrote {1}",
Date.Today.ToShortDateString, text)
End Sub
'Because Combine returns System.Delegate, with Option Strict On
'an explicit conversion is required.
Private CombinedDelegate As WriteTextMessage = CType(System.Delegate.
Combine(textWriter,
complexWriter),
WriteTextMessage)
'....
CombinedDelegate.Invoke("Test message")
In this scenario, you have two methods that behave differently, but both respect the delegate signature. A new delegate (CombinedDelegate
) is created invoking the System.Delegate.Combine
method that receives the series of delegates to be combined as arguments. It is worth mentioning that Combine
returns a System.Delegate
; therefore, an explicit conversion via CType
is required with Option Strict On
. With a single call to CombinedDelegate.Invoke
, you can call both WriteSomething
and WriteSomethingMoreComplex
. The preceding code produces the following output:
report your text: Test message
Today is 05/31/2012 and you wrote Test message
Delegate Keyword and System.Delegate
Delegate
is a reserved keyword in Visual Basic. Because of this, to invoke the System.Delegate.Combine
shared method, the full name of the class has been utilized. You can still take advantage of the shortened syntax including Delegate
within square brackets. In other words, you can write something like this: [Delegate].Combine(params())
. This works because the System
namespace is imported by default, and square brackets make the compiler consider the enclosed word as the identifier of a class exposed by one of the imported namespaces, instead of a reserved keyword.
Events are members that enable objects to send information on their state to the caller. When something occurs, an event tells the caller that something occurred so that the caller can make decisions on what actions to take. You handle events in UI-based applications, although not always. The .NET Framework takes a huge advantage from delegates to create event infrastructures, and this is what you can do in creating your custom events. In this section you first learn how to catch existing events, and then you get information on creating your own events. This approach is good because it provides a way to understand how delegates are used in event handling.
To provide your applications the capability of intercepting events raised from any object, you need to register for events. Registering means giving your code a chance to receive notifications and to take actions when it is notified that an event was raised from an object. To register for an event notification, you use the AddHandler
keyword that requires two arguments: The first one is the event exposed by the desired object, and the second is a delegate pointing to a method executed when your code is notified of an event occurring. The code in Listing 15.1 shows an example using a System.Timers.Timer
object.
Public Class EventsDemo
'Declares a Timer
Private myTimer As Timers.Timer
'A simple counter
Private counter As Integer
'Interval is the amount of time in ticks
Public Sub New(ByVal interval As Double)
'Register for notifications about the Elapsed event
AddHandler myTimer.Elapsed, AddressOf increaseCounter
'Assigns the Timer.Interval property
Me.myTimer.Interval = interval
Me.myTimer.Enabled = True
End Sub
'Method that adheres to the delegate signature and that is
'executed each time our class gets notifications about
'the Elapsed event occurring
Private Sub increaseCounter(ByVal sender As Object,
ByVal e As Timers.ElapsedEventArgs)
counter += 1
End Sub
End Class
Comments within Listing 15.1 should clarify the code. Notice how the AddHandler
instruction tells the runtime which event from the Timer
object must be intercepted (Elapsed
). Also notice how, via the AddressOf
keyword, you specify a method that performs some action when the event is intercepted. AddHandler
at this particular point requires the method to respect the ElapsedEventHandler
delegate signature. With this approach, the increaseCounter
method is executed every time the System.Timers.Timer.Elapsed
event is intercepted. AddHandler
provides great granularity on controlling events because it enables controlling shared events and works within a member body, too. The AddHandler
counterpart is RemoveHandler
, which enables deregistering from getting notifications. For example, you might want to deregister before a method completes its execution. Continuing with the example shown in Listing 15.1, you can deregister before you stop the timer:
RemoveHandler myTimer.Elapsed, AddressOf increaseCounter
Me.myTimer.Enabled = False
As you see in the next section, this is not the only way to catch events in Visual Basic.
By default, when you declare a variable for an object that exposes events, Visual Basic cannot see those events. This is also the case with the previous section’s example, where you declare a Timer
and then need to explicitly register for event handling. A solution to this scenario is to declare an object with the WithEvents
keyword, which makes events visible to Visual Basic. Thanks to WithEvents
, you do not need to register for events and can take advantage of the Handles
clause to specify the event a method is going to handle. The code in Listing 15.2 demonstrates this, providing a revisited version of the EventsDemo
class.
Public Class WithEventsDemo
Private WithEvents myTimer As Timers.Timer
Private counter As Integer
Public Sub New(ByVal interval As Double)
Me.myTimer.Interval = interval
Me.myTimer.Enabled = True
End Sub
Private Sub increaseCounter(ByVal sender As Object,
ByVal e As Timers.ElapsedEventArgs) _
Handles myTimer.Elapsed
counter += 1
End Sub
End Class
Notice that if you do not specify a Handles
clause, the code cannot handle the event, although it respects the appropriate delegate’s signature. It is worth mentioning that languages such as Visual C# do not have a WithEvents
counterpart, so you might prefer using AddHandler
and RemoveHandler
if you plan to write code that will be translated into or compared to other .NET languages.
Platform Exceptions
Exceptions to the last sentence are Windows Presentation Foundation (WPF), Silverlight, and WinRT. Because of the particular events’ infrastructure (routed events), the code can catch events even if you do not explicitly provide a Handles
clause. Of course, take care in this situation.
In the previous section, you learned how to handle existing events. Now it’s time to get your hands dirty on implementing and raising custom events within your own objects. Visual Basic provides two ways for implementing events: the Event
keyword and custom events. Let’s examine both of them.
You declare your own events by using the Event
keyword. This keyword requires you to specify the event name and eventually a delegate signature. Although not mandatory (Event
allows specifying no arguments), specifying a delegate signature is useful so that you can take advantage of AddHandler
for subsequently intercepting events. The code in Listing 15.3 shows an alternative implementation of the Person
class in which an event is raised each time the LastName
property is modified.
Public Class Person
Public Event LastNameChanged(ByVal sender As Object,
ByVal e As EventArgs)
Public Property FirstName As String
Private _lastName As String
Public Property LastName As String
Get
Return _lastName
End Get
Set(ByVal value As String)
If value <> _lastName Then
_lastName = value
RaiseEvent LastNameChanged(Me, EventArgs.Empty)
End If
End Set
End Property
End Class
Notice that in this particular case you need to implement the LastName
property the old-fashioned way, so you can perform subsequent manipulations. The code checks that the property value changes; then the LastNameChanged
event is raised. This is accomplished via the RaiseEvent
keyword. Also notice how the LastNameChanged
event definition adheres to the EventHandler
delegate signature. This can be considered as the most general delegate for the events infrastructure. The delegate defines two arguments: The first one, which is named sender, is of type Object
and represents the object that raised the event. The second is an argument, named e
, of type System.EventArgs
that is the base type for classes containing events information. You get a deeper example in the next section. At this point, intercepting the event is simple. You just need to register for event notifications or create an instance of the Person
class with WithEvents
. The following code demonstrates this:
Sub TestEvent()
Dim p As New Person
AddHandler p.LastNameChanged,
AddressOf personEventHandler
p.LastName = "Del Sole"
Console.ReadLine()
End Sub
Private Sub personEventHandler(ByVal sender As Object,
ByVal e As EventArgs)
Console.WriteLine("LastName property was changed")
End Sub
Now every time you change the value of the LastName
property, you can intercept the edit.
Property Change Notification in the Real World
The .NET Framework provides the INotifyPropertyChanged
interface, which is used to send notifications to the caller when the value of a property changes, by raising the PropertyChanged
event exposed by the interface. The current example shows a different technique because it is useful to make you understand how events work at a more general level.
In the previous code example, you got a basic idea about passing event information via the base System.EventArgs
class. In the .NET Framework, you can find hundreds of classes that inherit from System.EventArgs
and that enable passing custom event information to callers. This is useful whenever you need additional information on what happened during the event handling. Continuing with the previous example, imagine you want to check whether the LastName
property value contains blank spaces while you raise the LastNameChanged
event, sending this information to callers. This can be accomplished by creating a new class that inherits from System.EventArgs
. Listing 15.4 shows how you can implement the class and how you can take advantage of it in the Person
class.
Public Class LastNameChangedEventArgs
Inherits EventArgs
Private _lastName As String
Public ReadOnly Property LastName As String
Get
Return _lastName
End Get
End Property
Public ReadOnly Property ContainsBlank As Boolean
Get
Return Me.LastName.Contains(" ")
End Get
End Property
Public Sub New(ByVal lastName As String)
Me._lastName = lastName
End Sub
End Class
Public Class Person
Private _lastName As String
Public Property LastName As String
Get
Return _lastName
End Get
Set(ByVal value As String)
If value <> _lastName Then
_lastName = value
Dim e As New LastNameChangedEventArgs(value)
RaiseEvent LastNameChanged(Me, e)
End If
End Set
End Property
Public Event LastNameChanged(ByVal sender As Object,
ByVal e As LastNameChangedEventArgs)
End Class
Notice how the LastNameChangedEventArgs
class exposes the public properties representing information you want to return to the caller. When raising the event in the Person
class, you create a new instance of the LastNameChangedEventArgs
and pass the required information elaborated by the instance. Now you can change the event handler described in the previous section as follows:
Private Sub personEventHandler(ByVal sender As Object,
ByVal e As LastNameChangedEventArgs)
Console.WriteLine("LastName property was changed")
Console.WriteLine("Last name contains blank spaces: " &
e.ContainsBlank)
End Sub
In this way, you can easily handle additional event information. Finally, it is important to understand how you can get the instance of the object that raised the event because it is something that you will often use in your applications. You accomplish this by converting the sender into the appropriate type. The following code shows how to get the instance of the Person
class that raised the previous LastNameChanged
event:
Dim raisingPerson As Person = DirectCast(sender, Person)
Starting from Visual Basic 2005, you have had the ability to define your own events by implementing the custom events. Custom events are useful because they provide a kind of relationship with a delegate. They are also useful in multithreaded applications. You declare a custom event via the Custom Event
keywords combination, supplying the event name and signature as follows:
Public Custom Event AnEvent As EventHandler
AddHandler(ByVal value As EventHandler)
End AddHandler
RemoveHandler(ByVal value As EventHandler)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
End RaiseEvent
End Event
IntelliSense is very cool here because, when you type the event declaration and press Enter, it adds a skeleton for the custom event that is constituted by three members (see Figure 15.1): AddHandler
is triggered when the caller subscribes for an event with the AddHandler
instruction; RemoveHandler
is triggered when the caller removes an event registration; and RaiseEvent
is triggered when the event is raised. In this basic example, the new event is of the type EventHandler
, which is a delegate that represents an event storing no information—and that is the most general delegate.
Now take a look at the following example that demonstrates how to implement a custom event that can affect all instances of the Person
class:
Public Delegate Sub FirstNameChangedHandler(ByVal info As String)
Dim handlersList As New List(Of FirstNameChangedHandler)
Public Custom Event FirstNameChanged As FirstNameChangedHandler
AddHandler(ByVal value As FirstNameChangedHandler)
handlersList.Add(value)
Debug.WriteLine("AddHandler invoked")
End AddHandler
RemoveHandler(ByVal value As FirstNameChangedHandler)
If handlersList.Contains(value) Then
handlersList.Remove(value)
Debug.WriteLine("RemoveHandler invoked")
End If
End RemoveHandler
RaiseEvent(ByVal info As String)
'Performs the same action on all instances
'of the Person class
For Each del As FirstNameChangedHandler In handlersList
'del.Invoke(info ......
Next
End RaiseEvent
End Event
This code provides an infrastructure for handling changes on the FirstName
property in the Person
class. In this case, we build a list of delegates that is populated when the caller registers with AddHandler
. When the caller invokes RemoveHandler
, the delegate is popped from the list. The essence of this resides in the RaiseEvent
stuff, which implements a loop for performing the same operation on all instances of the delegate and therefore of the Person
class. To raise custom events, you use the RaiseEvent
keyword. For this, you need to edit the FirstName
property implementation in the Person
class as follows:
Private _firstName As String
Public Property FirstName As String
Get
Return _firstName
End Get
Set(ByVal value As String)
If value <> _firstName Then
_firstName = value
RaiseEvent FirstNameChanged(FirstName)
End If
End Set
End Property
You raise the event the same way for non-custom events, with the difference that custom events provide deep control over what is happening when the caller registers, deregisters, or raises the event.
Delegates are type-safe function pointers that store the address of a Sub
or Function
, enabling different methods to be invoked at runtime. Delegates are reference types declared via the Delegate
keyword; they enable invoking methods via the Invoke
method. They can be used in different programming techniques, but the most important scenario where you use delegates is within event-based programming. Events take advantage of delegates in that the objects require their signature to be respected. Coding events is something that can be divided into main areas, such as catching events and exposing events from your objects. To catch events, you can register and deregister via the AddHandler
and RemoveHandler
keywords, or you can declare objects by exposing events via the WithEvents
reserved word. Then you can provide event handlers respecting the appropriate delegate signature and adding the Handles
clause. You instead define your own events in two ways: via the Event
keyword or with custom events. The advantage of providing custom events is that you can have deep control over the event phases, such as registering and deregistering. In this discussion, it is important to remember that you can provide custom event information by creating classes that inherit from System.EventArgs
where you can store information useful to the caller.