|< Day Day Up >|
Events are built on top of delegates , which are types that represent references to methods . A delegate is declared just like a subroutine or a function.
Delegate Sub SubroutineDelegate(ByVal x As Integer, ByVal y As Integer) Delegate Function FunctionDelegate() As Integer
A variable whose type is a delegate type can then contain a reference to any method in any type that has the exact same set of parameters and the same return type. For example, the following code creates a new instance of a SubroutineDelegate that refers to the method Button.Move .
Class Button Public Sub Move(ByVal x As Integer, ByVal y As Integer) ... End Sub End Class Module Test Sub Main() Dim s As SubroutineDelegate Dim b As Button = New Button() s = New SubroutineDelegate(AddressOf b.Move) End Sub End Module
Once a delegate has been constructed , it can be passed around like any normal value. It can also be invoked just as a subroutine or function is invoked. When the delegate is invoked, it calls the method that the delegate refers to. In the following example, the delegate s is invoked and calls the method Button.Move .
Module Test Sub Main() Dim s As SubroutineDelegate Dim b As Button = New Button() s = New SubroutineDelegate(AddressOf b.Move) s(10, 20) End Sub End Module
The power of delegates is that they do not represent a reference to a particular method in a particular class, but instead represent a reference to a method with some general signature. Any method that matches the signature of the delegate can be referred to by that delegate. For example, the following function modifies the elements of an Integer array based on a custom modification function that's passed in as a delegate.
Module Test ' Delegate performs some action on the value. Delegate Function ModifyDelegate(ByVal Value As Integer) As Integer Sub ModifyArray(ByVal a() As Integer, ByVal Modify As ModifyDelegate) For Index As Integer = 0 To a.Length() - 1 a(Index) = Modify(a(Index)) Next Index End Sub ' Adds one to its argument Function AddOne(ByVal i As Integer) As Integer Return i + 1 End Function ' Divides its argument by two Function DivideByTwo(ByVal i As Integer) As Integer Return i \ 2 End Function Sub Main() Dim a(9) As Integer ' Add one to each element of the array ModifyArray(a, AddressOf AddOne) ' Divide each element of the array by two ModifyArray(a, AddressOf DivideByTwo) End Sub End Module
Because a delegate is a reference type, it must be created before it can be used. All delegate types have the same kind of constructor, which takes a single parameter. The argument to the constructor must be an AddressOf expression. The AddressOf operator takes either a shared method or an instance method qualified with an instance and produces a reference to that particular method. This reference is then stored in the delegate. For example:
Class Class1 Public Sub S1(ByVal x As Integer, ByVal y As Integer) MsgBox(x) End Sub Public Shared Sub S2(ByVal x As Integer, ByVal y As Integer) MsgBox(x) End Sub Public Function F() As Integer Return 10 End Function End Class Module Test Sub Main() Dim s1, s2 As SubroutineDelegate Dim f As FunctionDelegate Dim t As Class1 = New Class1() s1 = New SubroutineDelegate(AddressOf t.S1) s2 = New SubroutineDelegate(AddressOf Class1.S2) f = New FunctionDelegate(AddressOf t.F) End Sub End Module
Notice in the example that the shared method S2 was qualified with the class name ”in the case of shared methods, no instance is required to construct a delegate. If, however, the code had tried to say AddressOf Class1.S1 , a compile-time error would have occurred because an instance is required for instance methods.
As long as the type of delegate can reasonably be inferred from the surrounding context, an AddressOf expression can be used in place of the full delegate construction syntax. So the following code:
s1 = New SubroutineDelegate(AddressOf t.S1) s2 = New SubroutineDelegate(AddressOf Class1.S2) f = New FunctionDelegate(AddressOf t.F)
could have been written as follows :
s1 = AddressOf t.S1 s2 = AddressOf Class1.S2 f = AddressOf t.F
This is an example of a place where the type of the delegate cannot be inferred.
Dim o As Object ' Error: Delegate type cannot be inferred. o = AddressOf Class1.S2
In this case, the exact type of the delegate that needs to be constructed is not clear, because the value is being assigned to Object . This would produce a compile-time error.
Delegates are multicast , which means that a single delegate can contain a reference to more than one method. The methods can be completely different, as long as they have exactly the same set of parameters and the same return type. A delegate can refer to more than one method when it is combined with another delegate using the System.Delegate.Combine method. For example:
Class Button Public Sub Move(ByVal x As Integer, ByVal y As Integer) ... End Sub End Class Class Form Public Sub Move(ByVal x As Integer, ByVal y As Integer) ... End Sub End Class Module Test Sub Main() Dim s1, s2 As SubroutineDelegate Dim b As Button = New Button() Dim f As Form = New Form() s1 = AddressOf b.Move s2 = AddressOf f.Move s1 = CType(System.Delegate.Combine(s1, s2), SubroutineDelegate) s1(10,20) End Sub End Module
In this example, the delegate s1 ends up referring to both Button.Move and Form.Move . When the delegate is invoked, both methods are called, one after the other. The order in which the delegates are invoked is defined by the .NET Framework and cannot be relied upon.
Once delegates have been combined, the method System.Delegate.Remove can be used to break them apart. The following example only calls Button.Move because the reference to Form.Move has been removed from the delegate s1 .
Module Test Sub Main() Dim s1, s2 As SubroutineDelegate Dim b As Button = New Button() Dim f As Form = New Form() s1 = AddressOf b.Move s2 = AddressOf f.Move s1 = CType(System.Delegate.Combine(s1, s2), SubroutineDelegate) s1 = CType(System.Delegate.Remove(s1, s2), SubroutineDelegate) s1(10,20) End Sub End Module
When a delegate is invoked normally, it is invoked synchronously . This means that while the delegate is being invoked, the method that invoked the delegate sits and waits for the delegate to finish. However, delegates can also be invoked asynchronously . That is, the delegate can be invoked in such a way that the method that invoked the delegate can continue executing while the delegate is being invoked. Asynchronous delegate invocation is accomplished through use of multiple threads of execution, a topic that is beyond the scope of this book. However, a quick discussion of how asynchronous delegate invocation works may be worthwhile.
Every delegate type contains two methods called BeginInvoke and EndInvoke that allow the delegate to be called asynchronously. The parameters to BeginInvoke are the parameters to the delegate itself, a callback delegate to invoke when the delegate is finished executing, and an argument that should be passed in to the callback delegate. BeginInvoke returns an instance of an object that implements System.IAsyncResult , which can be used to monitor the progress of the delegate call. Thus, a delegate declared as follows:
Delegate Function CalculateDelegate(ByRef Iterations As Integer) _ As Integer
would have a BeginInvoke call that looked like this.
Function BeginInvoke(ByVal Iterations As Integer, _ ByVal DelegateCallback As AsyncCallback, _ ByVal DelegateAsyncState As Object) As IAsyncResult
When you call BeginInvoke , the .NET Framework queues the delegate in the thread pool. When a thread in the thread pool becomes available, the delegate is executed on that thread and runs until the delegate returns. When the delegate returns, the result of the delegate, if any, is stored in the object returned from BeginInvoke .
When the delegate call has completed, or if the code invoking the delegate wishes to block until the delegate call completes, EndInvoke is called on the delegate. EndInvoke takes the IAsyncResult from the BeginInvoke call as an argument and returns the value from the delegate call, if any. It also takes parameters for all the ByRef parameters in the delegate call ”this allows the delegate the chance to pass back the reference parameters. Thus, the EndInvoke method for the CalculateDelegate example would look like this.
Function EndInvoke(ByRef Iterations As Integer, _ ByVal DelegateAsyncResult As IAsyncResult) As _ Integer
The following code shows an example of using BeginInvoke and EndInvoke to invoke a method asynchronously.
Module Test Function Calculate(ByRef Iterations As Integer) As Integer Dim Result As Integer = 0 For Number As Integer = 1 To Iterations Result += Number Next Number Return Result End Function Delegate Function CalculateDelegate(ByRef Iterations As Integer) _ As Integer Sub Main() Dim d As CalculateDelegate = AddressOf Calculate Dim AsyncResult As IAsyncResult Dim Result As Integer Dim Iterations As Integer = 10000 AsyncResult = d.BeginInvoke(Iterations, Nothing, Nothing) Console.WriteLine("Waiting for the result...") Result = d.EndInvoke(Iterations, AsyncResult) Console.WriteLine("The result is " & Result & ".") End Sub End Module
|< Day Day Up >|