Chapter 15. Delegates and Events

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.

Understanding Delegates

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.


Where are Delegates Used?

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.

Declaring Delegates

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.


Advanced Delegates Techniques

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.


Combining Delegates: Multicast Delegates

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.


Handling Events

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.

Registering for Events: AddHandler and RemoveHandler

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.

Listing 15.1. Registering and Catching Events


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.

Declaring Objects with the WithEvents Keyword

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.

Listing 15.2. Catching Events with WithEvents and Handles


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.


Offering Events to the External World

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.

Raising Events

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.

Listing 15.3. Implementing and Raising Events


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.


Passing Event Information

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.

Listing 15.4. Providing Custom Event Information


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)

Creating Custom Events

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.

Image

Figure 15.1. IntelliSense generates a skeleton for all members of custom events.

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.135.107