The following is a valid delegate declaration: Public Delegate Function ProcessResultsByVal x As Double, ByVal y As Double _ As DoubleWhen the compiler encounters this line, it defines a
Trang 1Delegates and Events
Delegates provide a built-in, language-supported mechanism for defining and executing
callbacks Their flexibility allows you to define the exact signature of the callback, and that
information becomes part of the delegate type itself Anonymous functions are forms of
dele-gates that allow you to shortcut some of the delegate syntax that, in many cases, may be
overkill Building on top of delegates is the support for events in Visual Basic (VB) and the
.NET platform Events provide a uniform pattern for hooking up callback implementations,
and possibly multiple instances thereof, to the code that triggers the callback
Overview of Delegates
The common language runtime (CLR) provides a runtime that supports a flexible callback
mechanism, and delegates are the preferred method of implementing these callbacks When
you declare a delegate in your code, the VB compiler generates a class derived from
MulticastDelegate, and the CLR implements all of the interesting methods of the delegate
dynamically at run time
The delegate contains a couple of useful fields The first one holds a reference to anobject, and the second holds a method pointer When you invoke the delegate, the instance
method is called on the contained reference However, if the object reference is Nothing, the
runtime understands this to mean that the method is a shared method One delegate type can
handle callbacks to either an instance or shared method Moreover, invoking a delegate is
exactly the same syntactically as calling a regular function Therefore, delegates are perfect for
implementing callbacks
As you can see, delegates provide an excellent mechanism to decouple the method beingcalled on an instance from the actual caller In fact, the caller of the delegate has no idea, or
necessity to know, if it is calling an instance method or a shared method or on what exact
instance it is calling To the caller, it is calling arbitrary code The caller can obtain the delegate
instance through any appropriate means, and it can be decoupled completely from the entity
it actually calls
Think for a moment about UI elements in a dialog, such as a Commit button, and howmany external parties may be interested in knowing when that button is pressed If the class
that represents the button must call directly to the interested parties, it needs to have intimate
knowledge of the layout of those parties, or objects, and it must know which method to call on
each one of them Clearly, this requirement adds way too much coupling between the button
235
C H A P T E R 1 2
Trang 2class and the interested parties, and with coupling comes complexity Delegates come to therescue and break this link Now, interested parties need to only register a delegate with thebutton that is preconfigured to call whatever method they want This decoupling mechanismdescribes events as supported by the CLR The “Events” section discusses CLR events in moredetail Let’s go ahead and see how to create and use delegates.
Delegate Creation and Use
Delegate declarations look almost exactly like method declarations, except they have oneadded keyword: the Delegate keyword The following is a valid delegate declaration:
Public Delegate Function ProcessResults(ByVal x As Double, ByVal y As Double) _
As DoubleWhen the compiler encounters this line, it defines a type derived fromMulticastDelegate, which also implements a method named Invoke that has the exact samesignature of the method described in the delegate declaration For all practical purposes, thatclass looks like the following:
NotInheritable Class ProcessResults
Inherits System.MulticastDelegatePublic Function Invoke(ByVal x As Double, ByVal y As Double) As DoubleEnd Function
'Other stuff omitted for clarity
End Class
Even though the compiler creates a type similar to that listed, the compiler also abstractsthe use of delegates behind syntactical shortcuts In fact, the compiler won’t allow you to callthe Invoke method on a delegate directly Instead, you use a syntax that looks similar to afunction call, which we’ll show shortly
When you instantiate an instance of a delegate, you must wire it up to a method to callwhen it is invoked The method that you wire it up to could be either a shared or instancemethod that has a signature compatible with that of the delegate Thus, the parameter typesand the return type must either match the delegate declaration, or they must be implicitlyconvertible to the types in the delegate declaration
Trang 3Public Class Processor
Private factor As DoublePublic Sub New(ByVal factor As Double)Me.factor = factor
End SubPublic Function Compute(ByVal x As Double, ByVal y As Double) As DoubleDim result As Double = (x + y) * factor
Console.WriteLine("InstanceResults: {0}", result)Return result
End FunctionPublic Shared Function StaticCompute(ByVal x As Double, ByVal y As Double) _
As DoubleDim result As Double = (x + y) * 0.5Console.WriteLine("StaticResult: {0}", result)Return result
End FunctionEnd Class
Public Class EntryPoint
Shared Sub Main()Dim proc1 As Processor = New Processor(0.75)Dim proc2 As Processor = New Processor(0.83)Dim delegate1 As ProcessResults = _
New ProcessResults(AddressOf proc1.Compute)Dim delegate2 As ProcessResults = _
New ProcessResults(AddressOf proc2.Compute)Dim delegate3 As ProcessResults = _
New ProcessResults(AddressOf Processor.StaticCompute)Dim combined As Double = _
delegate1(4, 5) + delegate2(6, 2) + delegate3(5, 2)Console.WriteLine("Output: {0}", combined)
End SubEnd Class
Trang 4Running the previous code displays the following:
InstanceResults: 6.75
InstanceResults: 6.64
StaticResult: 3.5
Output: 16.89
This example creates three delegates Two point to instance methods, and one points
to a shared method Notice that you create the delegates by creating instances of the
ProcessResults type, which is the type created by the delegate declaration When you createthe delegate instances, you pass the methods they must call in the constructor Take note ofthe format of the parameter In the first two cases, you pass an instance method on the proc1and proc2 instances However, in the third case, you pass a method pointer on the type ratherthan an instance This is the way you create a delegate that points to a shared method ratherthan an instance method At the point where the delegates are called, the syntax is identicaland independent of whether the delegate points to an instance method or a shared method.This example is rather contrived, but it gives a clear indication of the basic usage of delegates
In all of the cases in the previous code, a single action takes place when the delegate iscalled It is also possible to chain delegates together so that multiple actions take place
Delegate Chaining
Delegate chaining allows you to create a linked list of delegates such that when the delegate atthe head of the list is called, all of the delegates in the chain are called The System.Delegateclass provides a few shared methods to manage lists of delegates To create delegate lists, yourely on the following methods declared inside of the System.Delegate type:
Public Class aDelegate
Implements ICloneableImplements ISerializablePublic Shared Function Combine(ByVal Delegates As aDelegate()) _
As aDelegateEnd FunctionPublic Shared Function Combine(ByVal First As aDelegate, _ByVal Second As aDelegate) As aDelegate
End FunctionEnd Class
Notice that the Combine methods take the delegates to combine and return another Delegate The Delegate returned is a new instance of a MulticastDelegate, because Delegateinstances are treated as immutable For example, the caller of Combine() may wish to create adelegate list but leave the original delegate instances in the same state they were in The onlyway to do that is to treat delegate instances as immutable when creating delegate chains.Notice that the first version of Combine() listed previously takes an array of delegates toform the constituents of the new delegate list, and the second form takes just a pair of dele-gates However, in both cases, any one of the Delegate instances could itself already be adelegate chain You can see that some fairly complex nesting can take place here
Trang 5To remove delegates from a list, you rely upon the following two shared methods on System.Delegate:
Public Class aDelegate
Implements ICloneableImplements ISerializablePublic Shared Function Remove(ByVal Source As aDelegate, _ByVal Value As aDelegate) As aDelegate
End FunctionPublic Shared Function RemoveAll(ByVal Source As aDelegate, _ByVal Value As aDelegate) As aDelegate
End FunctionEnd Class
As with the Combine methods, the Remove and RemoveAll methods return a new Delegateinstance created from the previous two The Remove method removes the last occurrence of
value in the source delegate list, whereas RemoveAll() removes all occurrences of the value
delegate list from the source delegate list Notice that the value parameter may represent
a delegate list rather than just a single delegate Again, these methods have the ability to meet
complex delegate list management needs
Let’s look at a modified form of the code example in the last section to see how you cancombine the delegates:
Imports System
Public Delegate Function ProcessResults(ByVal x As Double, ByVal y As Double) _
As DoublePublic Class Processor
Private factor As DoublePublic Sub New(ByVal factor As Double)Me.factor = factor
End SubPublic Function Compute(ByVal x As Double, ByVal y As Double) As DoubleDim Result As Double = (x + y) * factor
Console.WriteLine("InstanceResults: {0}", Result)Return Result
End FunctionPublic Shared Function StaticCompute(ByVal x As Double, ByVal y As Double) _
As DoubleDim Result As Double = (x + y) * 0.5
Trang 6Console.WriteLine("StaticResult: {0}", Result)Return Result
End FunctionEnd Class
Public Class EntryPoint
Shared Sub Main()Dim proc1 As Processor = New Processor(0.75)Dim proc2 As Processor = New Processor(0.83)Dim delegates As ProcessResults() = New ProcessResults() _{New ProcessResults(AddressOf proc1.Compute), _New ProcessResults(AddressOf proc2.Compute), _New ProcessResults(AddressOf Processor.StaticCompute)}
Dim chained As ProcessResults = _CType(System.Delegate.Combine(delegates), ProcessResults)Dim combined As Double = chained(4, 5)
Console.WriteLine("Output: {0}", combined)End Sub
on what your intentions are Finally, notice that before invoking the delegate chain, you mustcast the delegate back into the explicit delegate type This is necessary for the compiler toknow how to invoke the delegate The type returned from the Combine and Remove methods is
of type System.Delegate, which doesn’t have enough type information for the compiler to figure out how to invoke it
Trang 7Iterating Through Delegate Chains
Sometimes you need to call a chain of delegates, but you need to harvest the return values
from each invocation, or you may need to specify the ordering of the calls in the chain
For these times, the System.Delegate type, from which all delegates derive, offers the
GetInvocationList method to acquire an array of delegates where each element in the array
corresponds to a delegate in the invocation list Once you obtain this array, you can call the
delegates in any order you please, and you can process the return value from each delegate
appropriately You could also put an exception frame around each entry in the list so that an
exception in one delegate invocation will not abort the remaining invocations This modified
version of the previous example shows how to call each delegate in the chain explicitly:
Imports System
Public Delegate Function ProcessResults(ByVal x As Double, ByVal y As Double) _
As DoublePublic Class Processor
Private factor As DoublePublic Sub New(ByVal factor As Double)Me.factor = factor
End SubPublic Function Compute(ByVal x As Double, ByVal y As Double) As DoubleDim Result As Double = (x + y) * factor
Console.WriteLine("InstanceResults: {0}", Result)Return Result
End FunctionPublic Shared Function StaticCompute(ByVal x As Double, ByVal y As Double) _
As DoubleDim Result As Double = (x + y) * 0.5Console.WriteLine("StaticResult: {0}", Result)Return Result
End FunctionEnd Class
Public Class EntryPoint
Shared Sub Main()Dim proc1 As Processor = New Processor(0.75)Dim proc2 As Processor = New Processor(0.83)
Trang 8Dim delegates As ProcessResults() = New ProcessResults() _{New ProcessResults(AddressOf proc1.Compute), _New ProcessResults(AddressOf proc2.Compute), _New ProcessResults(AddressOf Processor.StaticCompute)}
Dim chained As ProcessResults = _CType(System.Delegate.Combine(delegates), ProcessResults)Dim chain As System.Delegate() = chained.GetInvocationList()Dim accumulator As Double = 0
For i As Integer = 0 To chain.Length - 1Dim current As ProcessResults = CType(chain(i), ProcessResults)accumulator += current(4, 5)
Next iConsole.WriteLine("Output: {0}", accumulator)End Sub
se, since it is bound to a method on a specific instance What if you want to have a delegaterepresent an instance method, and then you want to invoke that same delegate on a collection
of instances?
For this task, you need to use an open-instance delegate When you call a method on an
instance, a hidden parameter at the beginning of the parameter list, known as Me, representsthe current instance When you wire up a closed-instance delegate to an instance method on
an object instance, the delegate passes the object instance as the Me reference when it calls theinstance method With open-instance delegates, the delegate defers this action to the one whoinvokes the delegate Thus, you can provide the object instance to call on at delegate invoca-tion time
Let’s look at an example of what this would look like Imagine a collection of Employeetypes, and the company has decided to give everyone a 10% raise at the end of the year All of
Trang 9the Employee objects are contained in a collection type, and now you need to iterate over each
employee, applying the raise by calling the Employee.ApplyRaiseOf method:
Imports System
Imports System.Reflection
Imports System.Collections.Generic
Delegate Sub ApplyRaiseDelegate(ByVal emp As Employee, _
ByVal percent As Decimal)Public Class Employee
Private mSalary As DecimalPublic Sub New(ByVal salary As Decimal)Me.mSalary = salary
End SubPublic ReadOnly Property Salary() As DecimalGet
Return mSalaryEnd Get
End PropertyPublic Sub ApplyRaiseOf(ByVal percent As Decimal)mSalary *= 1 + percent
End SubEnd Class
Public Class EntryPoint
Shared Sub Main()Dim Employees As List(Of Employee) = New List(Of Employee)Employees.Add(New Employee(40000))
Employees.Add(New Employee(65000))Employees.Add(New Employee(95000))'Create open-instance delegateDim mi As MethodInfo = GetType(Employee).GetMethod("ApplyRaiseOf", _BindingFlags.Public Or BindingFlags.Instance)
Dim applyRaise As ApplyRaiseDelegate = _CType(System.Delegate.CreateDelegate(GetType(ApplyRaiseDelegate), _mi), ApplyRaiseDelegate)
'Apply raise
Dim e As Employee
Trang 10For Each e In EmployeesapplyRaise(e, CType(0.1, Decimal))'Send new salary to console.
Console.WriteLine("Employee's new salary = {0:C}", e.Salary)Next
End SubEnd Class
Here are the employees’ salaries after their raise:
Employee's new salary = $44,000.00
Employee's new salary = $71,500.00
Employee's new salary = $104,500.00
Notice that the declaration of the delegate has an Employee type declared at the beginning
of the parameter list This is how you expose the hidden instance pointer so that you can bind
it later Had you used this delegate to represent a closed-instance delegate, the Employeeparameter would have been omitted Unfortunately, VB doesn’t have any special syntax forcreating open-instance delegates Therefore, you must use one of the more generalized Delegate.CreateDelegate() overloads to create the delegate instance as shown, and beforeyou can do that, you must use reflection to obtain the MethodInfo instance representing themethod to bind to
The key point to notice here is that nowhere during the instantiation of the delegate doyou provide a specific object instance You won’t provide that until the point of delegate invo-cation The For Each loop shows how you invoke the delegate and provide the instance to callupon at the same time Even though the ApplyRaiseOf method that the delegate is wired totakes only one parameter, the delegate invocation requires two parameters, so that you canprovide the instance on which to make the call
The previous example shows how to create and invoke an open-instance delegate; ever, the delegate could still be more general and more useful in a broad sense In thatexample, you declared the delegate such that it knew it was going to be calling a method on atype of Employee Thus, at invocation time, you could have placed the call only on an instance
how-of Employee or a type derived from Employee You can use a generic delegate to declare the egate such that the type on which it is called is unspecified at declaration time.1Such adelegate is potentially much more useful It allows you to state the following: “I want to repre-sent a method that matches this signature supported by an as-of-yet unspecified type.” Only
del-at the point of instantidel-ation of the delegdel-ate are you required to provide the concrete type thdel-atwill be called Examine the following modifications to the previous example:
Imports System
Imports System.Reflection
Imports System.Collections.Generic
Delegate Sub ApplyRaiseDelegate(Of T)(ByVal instance As T, _
ByVal percent As Decimal)
1 The next chapter covers generics in more detail
Trang 11Public Class Employee
Private mSalary As DecimalPublic Sub New(ByVal salary As Decimal)Me.mSalary = salary
End SubPublic ReadOnly Property Salary() As DecimalGet
Return mSalaryEnd Get
End PropertyPublic Sub ApplyRaiseOf(ByVal percent As Decimal)mSalary *= 1 + percent
End SubEnd Class
Public Class EntryPoint
Shared Sub Main()Dim Employees As List(Of Employee) = New List(Of Employee)Employees.Add(New Employee(40000))
Employees.Add(New Employee(65000))Employees.Add(New Employee(95000))'Create open-instance delegateDim mi As MethodInfo = GetType(Employee).GetMethod("ApplyRaiseOf", _
Console.WriteLine("Employee's new salary = {0:C}", e.Salary)Next
End SubEnd Class
Trang 12Now, the delegate is much more generic Such a delegate could be useful in some stances For example, imagine an imaging program that supports applying filters to variousobjects on the canvas Suppose you need a delegate to represent a generic filter type that,when applied, is provided a percentage value to indicate how much of an effect it should have
circum-on the object Using generic, open-instance delegates, you could represent such a noticircum-on
Strategy Pattern
Delegates offer up a handy mechanism to implement the Strategy pattern In a nutshell, theStrategy pattern allows you to dynamically swap computational algorithms based upon theruntime situation For example, consider the common case of sorting a group of items Let’ssuppose that you want the sort to occur as quickly as possible However, due to system cir-cumstances, more temporary memory is required in order to achieve this speed This worksgreat for collections of reasonably manageable size, but if the collection grows to be huge, it’spossible that the amount of memory needed to perform the quick sort could exceed the sys-tem resource capacity For those cases, you can provide a sort algorithm that is much slowerbut uses far fewer resources The Strategy pattern allows you to swap out these algorithms atrun time depending on the conditions This example, although a tad contrived, illustrates thepurpose of the Strategy pattern perfectly
Typically, you implement the Strategy pattern using interfaces You declare an interfacethat all implementations of the strategy implement Then, the consumer of the algorithmdoesn’t care which concrete implementation of the strategy it is using Figure 12-1 features adiagram that describes this typical usage
Figure 12-1.Typical interface-based implementation of the Strategy pattern
Delegates offer a lighter-weight alternative to interfaces to implement a simple strategy.Interfaces are merely a mechanism to implement a programming contract Instead, imaginethat your delegate declaration is used to implement the contract, and any method thatmatches the delegate signature is a potential concrete strategy Now, instead of the consumerholding onto a reference to the abstract strategy interface, it simply holds onto a delegateinstance The following example illustrates this scenario:
Imports System
Imports System.Collections
Trang 13Public Delegate Function SortStrategy(ByVal theCollection As ICollection) As Array
Public Class Consumer
Private myCollection As ArrayListPrivate mStrategy As SortStrategyPublic Sub New(ByVal defaultStrategy As SortStrategy)Me.mStrategy = defaultStrategy
End SubPublic Property Strategy() As SortStrategyGet
Return mStrategyEnd Get
Set(ByVal value As SortStrategy)mStrategy = value
End SetEnd PropertyPublic Sub DoSomeWork()'Employ the strategy
Dim sorted As Array = mStrategy(myCollection)'Do something with the results
End SubEnd Class
Public Class SortAlgorithms
Private Shared Function SortFast(ByVal theCollection As ICollection) As Array'Do the fast sort
End FunctionPrivate Shared Function SortSlow(ByVal theCollection As ICollection) As Array'Do the slow sort
End FunctionEnd Class
When the Consumer object is instantiated, it is passed a default sort strategy, which isnothing more than a method that implements the SortStrategy delegate signature If the con-
ditions are right at run time, the sort strategy is swapped out and the Consumer.DoSomeWork
method automatically calls in the replacement strategy You could argue that implementing a
Strategy pattern this way is more flexible than using interfaces, since delegates can bind to
both shared methods and instance methods Therefore, you could create a concrete
imple-mentation of the strategy that also contains some state data that is needed for the operation,
as long as the delegate points to an instance method on a class that contains that state data
Trang 14In many cases, when you use delegates as a callback mechanism, you may want to notifysomeone that some event happened, such as a button press in a UI Suppose that you’redesigning a media player application Somewhere in the UI is a Play button In a well-
designed system, the UI and the control logic are separated by a well-defined abstraction,commonly implemented using a form of the Bridge pattern The abstraction facilitates imple-menting an alternate UI later, or, since UI operations are normally platform-specific, itfacilitates porting the application to another platform For example, the Bridge pattern workswell in situations where you want to decouple your control logic from the UI
By using the Bridge pattern, you can facilitate the scenario where changes that occur inthe core system don’t force changes in the UI, and conversely, where changes in the UI don’tforce changes in the core system One common way of implementing this pattern is by creat-ing well-defined interfaces into the core system that the UI then uses to communicate with it,and vice versa Delegates are an excellent mechanism to use to help define such an interface.With a delegate, you can begin to say things as abstract as, “When the user wants to play, Iwant you to call registered methods passing any information germane to the action.” Thebeauty here is that the core system doesn’t care how the user indicates to the UI that he wantsthe player to start playing media It could be a button press, a menu selection, or a brain-wavedetection device that recognizes what the user is thinking To the core system, it doesn’t mat-ter, and you can change and interchange both independently without breaking the other Bothsides adhere to the same agreed-upon interface contract, which in this case is a specificallyformed delegate and a means to register that delegate with the event-generating entity.2
This pattern of usage, also known as publish-subscribe, is so common, even outside therealm of UI development, that the NET runtime designers defined a formalized built-in eventmechanism When you declare an event within a class, the compiler implements some hiddenmethods that allow you to register and unregister delegates that get called when a specificevent is raised In essence, an event is a shortcut that saves you the time of having to write theregister and unregister methods that manage a delegate chain Let’s take a look at a simpleevent sample based on the previous discussion:
Imports System
'Arguments passed from UI when play event occurs
Public Class PlayEventArgs
Inherits EventArgsPrivate mFilename As StringPublic Sub New(ByVal filename As String)Me.mFilename = filename
End SubPublic ReadOnly Property Filename() As StringGet
Return mFilenameEnd Get
2 Chapter 7 covers the topics of contracts and interfaces in detail
Trang 15End PropertyEnd Class
Public Class PlayerUI
'Define event for play notifications
Public Event PlayEvent As EventHandler(Of PlayEventArgs)Public Sub UserPressedPlay()
OnPlay()End SubProtected Overridable Sub OnPlay()'Fire the event
Dim localHandler As EventHandler(Of PlayEventArgs) = PlayEventEvent
If Not localHandler Is Nothing ThenlocalHandler(Me, New PlayEventArgs("song.wav"))End If
End SubEnd Class
Public Class CorePlayer
Private ui As PlayerUIPublic Sub New()
ui = New PlayerUI()'Register our event handler
AddHandler ui.PlayEvent, AddressOf PlaySomethingEnd Sub
Private Sub PlaySomething(ByVal source As Object, ByVal args As PlayEventArgs)'Play the file
End SubEnd Class
Public Class EntryPoint
Shared Sub Main()Dim player As CorePlayer = New CorePlayer()End Sub
End Class
Even though the syntax of this simple event may look complicated, the overall idea is thatyou’re creating a well-defined interface through which to notify interested parties that the userwants to play a file That well-defined interface is encapsulated inside the PlayEventArgs class
Trang 16Events put certain rules upon how you use delegates The delegate must not return anything,and it must accept two arguments The first argument is an object reference representing theparty generating the event The second argument must be a type derived from
System.EventArgs Your EventArgs derived class is where you define any event-specific arguments
Notice that we’ve declared the event using the generic EventHandler(Of T) class Theevent is defined within the PlayerUI class using the Event keyword The Event keyword is firstfollowed by the name of the event, PlayEvent, and then followed by the defined event dele-gate The PlayEvent identifier means two entirely different things depending on what side ofthe decoupling fence you’re on From the perspective of the event generator—in this case,PlayerUI—the PlayEvent event is used just like a delegate You can see this usage inside theOnPlay method Typically, a method such as OnPlay is called in response to a UI button press
It notifies all of the registered listeners by calling through the PlayEvent event (delegate)
■ Note The popular idiom when raising events is to raise the event within a Protected Overridable
method named On<event>, where <event>is replaced with the name of the event—in this case,OnPlay.This way, derived classes can easily modify the actions taken when the event needs to be raised In VB, youmust test the event for Nothingbefore calling it; otherwise, the result could be a
NullReferenceException The OnPlay()method makes a local copy of the event before testing it for
Nothing This avoids the race condition where the event is set to Nothingfrom another thread after the
Nothingcheck passes and before the event is raised
From the event consumer side of the fence, the PlayEvent identifier is used completelydifferently As you can see in the CorePlayer constructor, the AddHandler statement is used toregister the event listener
That’s the basic structure of events As alluded to earlier, NET events are a shortcut to creating delegates and the interfaces with which to register those delegates As proof of this, you can examine the intermediate language (IL) generated from compiling the previousexample to see that the compiler has generated two methods, add_PlayEvent() and
remove_PlayEvent(), which get called when you use the AddHandler and RemoveHandlerstatements These statements manage the addition and removal of delegates from the eventdelegate chain
Public Class PlayerUI
'Define event for play notifications
Private PlayEventEvent As EventHandler(Of PlayEventArgs)
Trang 17Public Custom Event PlayEvent As EventHandler(Of PlayEventArgs)AddHandler(ByVal value As EventHandler(Of PlayEventArgs))PlayEventEvent = _
CType(System.Delegate.Combine(PlayEventEvent, value), _EventHandler(Of PlayEventArgs))
End AddHandlerRemoveHandler(ByVal value As EventHandler(Of PlayEventArgs))PlayEventEvent = _
CType(System.Delegate.Remove(PlayEventEvent, value), _EventHandler(Of PlayEventArgs))
End RemoveHandlerRaiseEvent(ByVal sender As Object, ByVal e As PlayEventArgs)
If Not sender Is Nothing Then'Add event code here
End IfEnd RaiseEventEnd Event
Public Sub UserPressedPlay()OnPlay()
End SubProtected Overridable Sub OnPlay()'Fire the event
Dim localHandler As EventHandler(Of PlayEventArgs) = PlayEventEvent
If Not localHandler Is Nothing ThenlocalHandler(Me, New PlayEventArgs("song.wav"))End If
End SubEnd Class
Inside the AddHandler and RemoveHandler sections of the Event declaration, the delegatebeing added or removed is referenced through the value keyword, which is identical to the
way property setters work This example uses Delegate.Combine() and Delegate.Remove() to
manage an internal delegate chain This example is a bit contrived because the default event
mechanism does essentially the same thing, but we show it here for the sake of example
One final comment regarding design patterns is in order You can see that events are idealfor implementing a publish-subscribe design pattern, where many listeners register for notifi-
cation (publication) of an event Similarly, you can use NET events to implement a form of
the Observer pattern, where various entities register to receive notifications that some other
entity has changed
Trang 18Delegates offer a first-class system-defined and system-implemented mechanism for formly representing callbacks In this chapter, you saw various ways to declare and createdelegates of different types, including single delegates, chained delegates, and open-instancedelegates Additionally, we showed how to use delegates as the building blocks of events Youcan use delegates to implement a wide variety of design patterns, since delegates are a greatmeans for defining a programming contract And at the heart of just about all design patterns
uni-is a well-defined contract
The next chapter will cover the details of generics, which is arguably one of the mostexciting additions to the VB language
Trang 19Support for generics is one of the biggest additions to VB 2005 and NET 2.0 Generics allows
you to create open-ended types that are converted into closed types at run time Each unique
closed type is itself a unique type, and only closed types may be instantiated
Introduction to Generics
Generics creates open-ended types through the use of type parameters The new Of keyword,
followed by a type argument, specifies the type you should use when creating the generic type
or executing the generic member at run time Generics also eliminates the need for boxing
and unboxing operations You can also use generics to increase type safety by specifying type
End Class
In this case, you declare a generic type, MyCollection(Of T), which treats the type withinthe collection as an unspecified type In this example, the type parameter list consists of only
one type, and it is described with syntax where the generic type is listed between parentheses
The identifier T is really just a placeholder for any type At some point, a consumer of
MyCollection(Of T) declares what’s called a closed type, by specifying the concrete type that
T is supposed to represent For example, suppose some other assembly wants to create a
MyCollection(Of T) constructed type that contains members of type Integer Then it would
do so as shown in the following code:
Public Sub SomeMethod()
Dim collectionOfNumbers As MyCollection(Of Integer) = _New MyCollection(Of Integer)()
End Sub
253
C H A P T E R 1 3
Trang 20MyCollection(Of Integer) in the previous code is the closed type MyCollection(Of Integer) is usable just as any other declared type, and it also follows all of the same rulesthat other nongeneric types follow The only difference is that it was born from a generic type.
At the point of instantiation, the intermediate language (IL) code behind the implementation
of MyCollection(Of T) gets just-in-time (JIT)-compiled in a way that all of the usages of type
T in the implementation of MyCollection(Of T) get replaced with type Integer
Note that all unique constructed types created from the same generic type are, in fact,completely different types that share no implicit conversion capabilities For example,
MyCollection(Of Long) is a completely different type than MyCollection(Of Integer), andyou cannot do something like the following:
'THIS WILL NOT WORK!
Public Sub SomeMethod(ByVal intNumbers As MyCollection(Of Integer))
Dim longNumbers As MyCollection(Of Long) = intNumbers ' ERROR!
End Subthen you might be surprised to learn that you cannot accomplish the same idea using con-structed generic types The difference is that with array covariance, the source and the
destination of the assignment are of the same type, System.Array The array covariance rulessimply allow you to assign one array from another, as long as the declared type of the elements
in the array are implicitly convertible at compile time However, in the case of two constructedgeneric types, they are completely separate types
Efficiency and Type Safety of Generics
Efficiency is arguably one of the greatest gains from generics in VB A regular array, based onSystem.Array, can contain a heterogeneous collection of instances created from many types.This does, however, come with its drawbacks Take a look at the following usage:
Public Sub SomeMethod(ByVal col As ArrayList)
For Each o As Object In colDim iface As ISomeInterface = CType(o, ISomeInterface)o.DoSomething()
Next oEnd Sub
Trang 21Since everything in the common language runtime (CLR) is derived from System.Object,the ArrayList passed in via the col parameter could possibly contain a hodgepodge of things.
Some of those things may not actually implement ISomeInterface As you’d expect, an
InvalidCastException could erupt from this code However, wouldn’t it be nice to be able to
utilize the compiler’s type engine to help sniff out such things at compile time? That’s exactly
what generics allows you to do Using generics, you can devise something like the following:
Public Sub SomeMethod(ByVal col As IList(Of ISomeInterface))
For Each iface As ISomeInterface In colo.DoSomething()
Next ifaceEnd Sub
In the previous example, the method accepts an interface of IList(Of T) Since the typeparameter to the constructed type is of type ISomeInterface, the only type of objects that the
list may hold are those of type ISomeInterface Now the compiler has everything it needs to
enforce strong type safety
■ Note Added type safety at compile time is a good thing, because it’s better to capture bugs based on
type mismatches at compile time rather than later at run time
The previous example shows how to use generics for better type safety However, youhaven’t gained much yet from an efficiency standpoint The real efficiency gain comes into
play when the type argument is a value type Remember that a value type inserted into a
col-lection in the System.Colcol-lections namespace, such as ArrayList, must first be boxed, since
the ArrayList maintains a collection of System.Object types An ArrayList meant to hold
nothing but a bunch of integers suffers from severe efficiency problems, since you must box
and unbox the integers each time you insert and reference or extract them from the ArrayList,
respectively Also, an unboxing operation in VB is normally formed with an IL unbox operation
paired with a copy operation on the value type’s data Generics comes to the rescue and stops
the box/unbox cycle As an example, compile the following code, and then load the assembly
into the Microsoft Intermediate Language (MSIL) Disassembler to compare the IL generated
for each of the methods that accepts a stack:
Imports System
Imports System.Collections
Imports System.Collections.Generic
Public Class EntryPoint
Shared Sub Main()End Sub
Public Sub NonGeneric(ByVal stack As Stack)For Each o As Object In stack
Dim number As Integer = CInt(Fix(o))