1. Trang chủ
  2. » Công Nghệ Thông Tin

Accelerated VB 2005 phần 7 doc

43 154 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Nội dung

Delegates 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 an object, 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 being called 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 how many 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 CHAPTER 12 801-6CH12.qxd 2/28/07 3:30 AM Page 235 class and the interested parties, and with coupling comes complexity. Delegates come to the rescue and break this link. Now, interested parties need to only register a delegate with the button that is preconfigured to call whatever method they want. This decoupling mechanism describes events as supported by the CLR. The “Events” section discusses CLR events in more detail. 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 one added keyword: the Delegate keyword. The following is a valid delegate declaration: Public Delegate Function ProcessResults(ByVal x As Double, ByVal y As Double) _ As Double When the compiler encounters this line, it defines a type derived from MulticastDelegate, which also implements a method named Invoke that has the exact same signature of the method described in the delegate declaration. For all practical purposes, that class looks like the following: NotInheritable Class ProcessResults Inherits System.MulticastDelegate Public Function Invoke(ByVal x As Double, ByVal y As Double) As Double End Function 'Other stuff omitted for clarity. End Class Even though the compiler creates a type similar to that listed, the compiler also abstracts the use of delegates behind syntactical shortcuts. In fact, the compiler won’t allow you to call the Invoke method on a delegate directly. Instead, you use a syntax that looks similar to a function call, which we’ll show shortly. When you instantiate an instance of a delegate, you must wire it up to a method to call when it is invoked. The method that you wire it up to could be either a shared or instance method that has a signature compatible with that of the delegate. Thus, the parameter types and the return type must either match the delegate declaration, or they must be implicitly convertible to the types in the delegate declaration. Single Delegate The following example shows the basic syntax of how to create a delegate: Imports System Public Delegate Function ProcessResults(ByVal x As Double, ByVal y As Double) _ As Double CHAPTER 12 ■ DELEGATES AND EVENTS236 801-6CH12.qxd 2/28/07 3:30 AM Page 236 Public Class Processor Private factor As Double Public Sub New(ByVal factor As Double) Me.factor = factor End Sub Public Function Compute(ByVal x As Double, ByVal y As Double) As Double Dim result As Double = (x + y) * factor Console.WriteLine("InstanceResults: {0}", result) Return result End Function Public Shared Function StaticCompute(ByVal x As Double, ByVal y As Double) _ As Double Dim result As Double = (x + y) * 0.5 Console.WriteLine("StaticResult: {0}", result) Return result End Function End 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 Sub End Class CHAPTER 12 ■ DELEGATES AND EVENTS 237 801-6CH12.qxd 2/28/07 3:30 AM Page 237 Running 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 create the delegate instances, you pass the methods they must call in the constructor. Take note of the format of the parameter. In the first two cases, you pass an instance method on the proc1 and proc2 instances. However, in the third case, you pass a method pointer on the type rather than an instance. This is the way you create a delegate that points to a shared method rather than an instance method. At the point where the delegates are called, the syntax is identical and 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 is called. 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 at the head of the list is called, all of the delegates in the chain are called. The System.Delegate class provides a few shared methods to manage lists of delegates. To create delegate lists, you rely on the following methods declared inside of the System.Delegate type: Public Class aDelegate Implements ICloneable Implements ISerializable Public Shared Function Combine(ByVal Delegates As aDelegate()) _ As aDelegate End Function Public Shared Function Combine(ByVal First As aDelegate, _ ByVal Second As aDelegate) As aDelegate End Function End 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 Delegate instances are treated as immutable. For example, the caller of Combine() may wish to create a delegate list but leave the original delegate instances in the same state they were in. The only way 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 to form 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 a delegate chain. You can see that some fairly complex nesting can take place here. CHAPTER 12 ■ DELEGATES AND EVENTS238 801-6CH12.qxd 2/28/07 3:30 AM Page 238 To remove delegates from a list, you rely upon the following two shared methods on System.Delegate: Public Class aDelegate Implements ICloneable Implements ISerializable Public Shared Function Remove(ByVal Source As aDelegate, _ ByVal Value As aDelegate) As aDelegate End Function Public Shared Function RemoveAll(ByVal Source As aDelegate, _ ByVal Value As aDelegate) As aDelegate End Function End Class As with the Combine methods, the Remove and RemoveAll methods return a new Delegate instance 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 can combine the delegates: Imports System Public Delegate Function ProcessResults(ByVal x As Double, ByVal y As Double) _ As Double Public Class Processor Private factor As Double Public Sub New(ByVal factor As Double) Me.factor = factor End Sub Public Function Compute(ByVal x As Double, ByVal y As Double) As Double Dim Result As Double = (x + y) * factor Console.WriteLine("InstanceResults: {0}", Result) Return Result End Function Public Shared Function StaticCompute(ByVal x As Double, ByVal y As Double) _ As Double Dim Result As Double = (x + y) * 0.5 CHAPTER 12 ■ DELEGATES AND EVENTS 239 801-6CH12.qxd 2/28/07 3:30 AM Page 239 Console.WriteLine("StaticResult: {0}", Result) Return Result End Function End 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 End Class Running this form of the code displays the following: InstanceResults: 6.75 InstanceResults: 7.47 StaticResult: 4.5 Output: 4.5 Notice that instead of calling all of the delegates, this example chains them together and then calls them by calling through the head of the chain. This example features some major differences from the previous example. First of all, the resultant Double that comes out of the chained invocation is the result of the last delegate called, which, in this case, is the delegate pointing to the shared method StaticCompute. The return values from the other delegates in the chain are simply lost. Also, if any of the delegates throws an exception, processing of the delegate chain will terminate and the CLR will begin to search for the next exception-handling frame on the stack. Be aware that if you declare delegates that take parameters by reference, then each delegate that uses the reference parameter will see the changes made by the previ- ous delegate in the chain. This could be a desired effect, or it could be a surprise, depending on what your intentions are. Finally, notice that before invoking the delegate chain, you must cast the delegate back into the explicit delegate type. This is necessary for the compiler to know 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. CHAPTER 12 ■ DELEGATES AND EVENTS240 801-6CH12.qxd 2/28/07 3:30 AM Page 240 Iterating 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 Double Public Class Processor Private factor As Double Public Sub New(ByVal factor As Double) Me.factor = factor End Sub Public Function Compute(ByVal x As Double, ByVal y As Double) As Double Dim Result As Double = (x + y) * factor Console.WriteLine("InstanceResults: {0}", Result) Return Result End Function Public Shared Function StaticCompute(ByVal x As Double, ByVal y As Double) _ As Double Dim Result As Double = (x + y) * 0.5 Console.WriteLine("StaticResult: {0}", Result) Return Result End Function End Class Public Class EntryPoint Shared Sub Main() Dim proc1 As Processor = New Processor(0.75) Dim proc2 As Processor = New Processor(0.83) CHAPTER 12 ■ DELEGATES AND EVENTS 241 801-6CH12.qxd 2/28/07 3:30 AM Page 241 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 chain As System.Delegate() = chained.GetInvocationList() Dim accumulator As Double = 0 For i As Integer = 0 To chain.Length - 1 Dim current As ProcessResults = CType(chain(i), ProcessResults) accumulator += current(4, 5) Next i Console.WriteLine("Output: {0}", accumulator) End Sub End Class Calling each delegate in the chain explicitly displays the following: InstanceResults: 6.75 InstanceResults: 7.47 StaticResult: 4.5 Output: 18.72 Open-Instance Delegates All of the delegate examples so far show how to wire up a delegate to a shared method on a specific type or to an instance method on a specific instance. This abstraction provides excel- lent decoupling, but the delegate doesn’t really imitate or represent a pointer to a method per se, since it is bound to a method on a specific instance. What if you want to have a delegate represent 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, represents the 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 the instance method. With open-instance delegates, the delegate defers this action to the one who invokes 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 Employee types, and the company has decided to give everyone a 10% raise at the end of the year. All of CHAPTER 12 ■ DELEGATES AND EVENTS242 801-6CH12.qxd 2/28/07 3:30 AM Page 242 the 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 Decimal Public Sub New(ByVal salary As Decimal) Me.mSalary = salary End Sub Public ReadOnly Property Salary() As Decimal Get Return mSalary End Get End Property Public Sub ApplyRaiseOf(ByVal percent As Decimal) mSalary *= 1 + percent End Sub End 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 delegate Dim 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 CHAPTER 12 ■ DELEGATES AND EVENTS 243 801-6CH12.qxd 2/28/07 3:30 AM Page 243 For Each e In Employees applyRaise(e, CType(0.1, Decimal)) 'Send new salary to console. Console.WriteLine("Employee's new salary = {0:C}", e.Salary) Next End Sub End 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 Employee parameter would have been omitted. Unfortunately, VB doesn’t have any special syntax for creating open-instance delegates. Therefore, you must use one of the more generalized Delegate.CreateDelegate() overloads to create the delegate instance as shown, and before you can do that, you must use reflection to obtain the MethodInfo instance representing the method to bind to. The key point to notice here is that nowhere during the instantiation of the delegate do you 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 call upon at the same time. Even though the ApplyRaiseOf method that the delegate is wired to takes only one parameter, the delegate invocation requires two parameters, so that you can provide the instance on which to make the call. The previous example shows how to create and invoke an open-instance delegate; how- ever, the delegate could still be more general and more useful in a broad sense. In that example, you declared the delegate such that it knew it was going to be calling a method on a type of Employee. Thus, at invocation time, you could have placed the call only on an instance of Employee or a type derived from Employee. You can use a generic delegate to declare the del- egate such that the type on which it is called is unspecified at declaration time. 1 Such a delegate 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 at the point of instantiation of the delegate are you required to provide the concrete type that will 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) CHAPTER 12 ■ DELEGATES AND EVENTS244 1. The next chapter covers generics in more detail. 801-6CH12.qxd 2/28/07 3:30 AM Page 244 [...]... well-defined contract The next chapter will cover the details of generics, which is arguably one of the most exciting additions to the VB language 801-6CH13.qxd 2/28/ 07 3:34 AM CHAPTER Page 253 13 Generics S upport 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... type Only field1 is a closed type, whereas field2 is an open type, since its final type must still be determined at run time based on the type arguments from A(Of T) 2 57 801-6CH13.qxd 258 2/28/ 07 3:34 AM Page 258 CHAPTER 13 ■ GENERICS In VB, all name declarations are declared and are valid within a specific scope Within the confines of a method, for example, any local variable identifiers declared within... In shapes 'DON'T DO THIS! Dim theShape As IShape = CType(shape, IShape) acc += theShape.Area Next shape Return acc End Get End Property Public Sub Add(ByVal shape As T) 269 801-6CH13.qxd 270 2/28/ 07 3:34 AM Page 270 CHAPTER 13 ■ GENERICS shapes.Add(shape) End Sub End Class This modification to Shapes(Of T) indeed does compile and work, most of the time However, this generic has lost some value due... remove_PlayEvent(), which get called when you use the AddHandler and RemoveHandler statements These statements manage the addition and removal of delegates from the event delegate chain Custom Events VB 2005 has added a new keyword, Custom, as a modifier for the Event statement Custom events allow you to specify actions when your code adds/removes an event handler or raises an event You accomplish this... reference to the abstract strategy interface, it simply holds onto a delegate instance The following example illustrates this scenario: Imports System Imports System.Collections 801-6CH12.qxd 2/28/ 07 3:30 AM Page 2 47 CHAPTER 12 ■ DELEGATES AND EVENTS Public Delegate Function SortStrategy(ByVal theCollection As ICollection) As Array Public Class Consumer Private myCollection As ArrayList Private mStrategy... generics in VB A regular array, based on System.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 col Dim iface As ISomeInterface = CType(o, ISomeInterface) o.DoSomething() Next o End Sub 801-6CH13.qxd 2/28/ 07 3:34 AM Page... type via a generic, as the following code shows: Public Class A(Of T) Private innerObject As T End Class Public Class Consumer(Of T) Private obj As A(Of Stack(Of T)) End Class 801-6CH13.qxd 2/28/ 07 3:34 AM Page 2 57 CHAPTER 13 ■ GENERICS In this case, a generic type, Consumer(Of T), is defined and also contains a field that is based on another generic type When declaring the type of the Consumer(Of T).obj... implementation 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 2 47 801-6CH12.qxd 248 2/28/ 07 3:30 AM Page 248 CHAPTER 12 ■ DELEGATES AND EVENTS Events In many cases, when you use delegates as a callback mechanism, you may want to notify someone that some event happened, such as... 801-6CH13.qxd 266 2/28/ 07 3:34 AM Page 266 CHAPTER 13 ■ GENERICS Public Sub New(ByVal firstName As String, ByVal lastName As String) Me.firstName = firstName Me.lastName = lastName Me.terminationDate = Nothing Me.ssn = Nothing End Sub End Class Public Class EntryPoint Shared Sub Main() Dim emp As Employee = New Employee("Jodi", "Fouche") Dim tempSSN As Long emp.ssn = 12345 678 9 Console.WriteLine("{0}... consider what it means to assign a nullable type to a non-nullable type For example, in the Main method, you want to assign tempSSN based upon the value of emp.ssn However, since 801-6CH13.qxd 2/28/ 07 3:34 AM Page 2 67 CHAPTER 13 ■ GENERICS emp.ssn is nullable, what should tempSSN be assigned to if emp.ssn happens to have no value? You use the HasValue property to inspect emp.ssn and assign it to -1 should . Class CHAPTER 12 ■ DELEGATES AND EVENTS 2 37 801-6CH12.qxd 2/28/ 07 3:30 AM Page 2 37 Running the previous code displays the following: InstanceResults: 6 .75 InstanceResults: 6.64 StaticResult: 3.5 Output:. delegate in the chain explicitly displays the following: InstanceResults: 6 .75 InstanceResults: 7. 47 StaticResult: 4.5 Output: 18 .72 Open-Instance Delegates All of the delegate examples so far show how. additions to the VB language. CHAPTER 12 ■ DELEGATES AND EVENTS252 801-6CH12.qxd 2/28/ 07 3:30 AM Page 252 Generics Support for generics is one of the biggest additions to VB 2005 and .NET 2.0.

Ngày đăng: 09/08/2014, 12:22

TỪ KHÓA LIÊN QUAN

w