7.1 DelegatesIn a never-ending effort to deny VB programmers the right to use pointers, Microsoft has implemented a feature called delegates that, according to the documentation, provide a safe alternative to function pointers. As you may know, a pointer variable (or pointer ) is simply a variable whose value is interpreted by the compiler as a memory address. The address to which the pointer points is the target of the pointer, and we say that the pointer variable points to that target address. If the target address is a variable of data type Integer, for example, then we say that the pointer is of type Integer or is an Integer pointer. Thus, the type of a pointer is the type of the target variable. (We have seen that, as reference types, variables of type Object and String are both pointers; i.e., their values point to the address of the data in memory.) A pointer can also point to a function, that is, contain the address of a function. Even though a function is not a variable, it does have a physical location in memory and so can be the target of a pointer. (Actually, it's reasonable to think of a function as a type of variable, but that is another story.) In this case, we have a function pointer . Function pointers are very useful in certain situations for calling or specifying functions. This is commonly done in the C++ programming language, where function pointers are supported directly. One area in which function pointers are used is in the context of callback functions . To illustrate , if we want to enumerate all of the fonts on a given system, the Windows API provides a function called EnumFontFamiliesEx , defined as follows : Public Declare Function EnumFontFamiliesEx Lib "gdi32" _ Alias "EnumFontFamiliesExA" ( _ ByVal hdc As Long, _ lpLogFont As LOGFONT, _ ByVal lpEnumFontProc As Long, _ ByVal lParam As Long, _ ByVal dw As Long) _ As Long The third parameter requires the address of a function we must declare, called a callback function. The reason for this term is that Windows will call our callback function for each font in the system, passing information about the font in the parameters of the function. According to the documentation, the callback function must have a particular form: Public Function EnumFontFamExProc(ByVal lpelfe As Long, _ ByVal lpntme As Long, _ ByVal FontType As Long, ByRef lParam As Long) As Long The point here is that to use EnumFontFamiliesEx , we need to pass the address of a function as one of the parameters. As you may know, this is done in VB using the AddressOf operator. In earlier versions of VB, this operator is described as follows:
Put another way, the AddressOf operator is implemented in VB 6 for the express purpose of passing function addresses to API functions. In VB.NET, the AddressOf operator returns a delegate, which is, as the documentation states:
So let us discuss delegates. We begin with a rather unhelpful definition: a delegate is an object of a class derived from either the Delegate class or the MulticastDelegate class. These two classes are abstract, so no objects of these classes can be created. Nevertheless, other classes can be derived from these classes, and objects can be created from these derived classes. In VB.NET, delegates can be used to call methods of objects or to supply callback functions. In addition, VB.NET uses delegates to bind event handlers to events. Fortunately, VB.NET also supplies tools (such as the AddHandler method) to automate this process, so we don't need to use delegates directly for this purpose. A delegate object inherits a number of properties and methods from the Delegate or MulticastDelegate class. In particular, a delegate object has:
By now you have probably guessed that there are two delegate classes because delegates derived from the Delegate class can only call a single method, whereas delegates derived from MulticastDelegate can call multiple methods. 7.1.1 Using a Delegate to Call a MethodTo call a method using a delegate, we call the Invoke method of the delegate. To illustrate, consider the class module with a simple method: Public Class Class1 Public Sub AMethod(ByVal s As String) Msgbox(s) End Sub End Class Now, in a module with a Windows Form (referred to as a form module in earlier versions of VB), we declare a (single cast) delegate with the same parameters as the target method we wish to call: Delegate Sub AMethodDelegate(ByVal s As String) The following code then uses the delegate to call the AMethod of Class1: Protected Sub Form1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Click ' Object of type Class1 _ Dim obj As New Class1( ) ' Declare a new delegate Dim delg As ADelegate ' Define the delegate, passing the address ' of the object's method delg = New ADelegate(AddressOf obj.AMethod) ' Now call the method using the delegate's Invoke method delg.Invoke("test") End Sub Note that the documentation describes the delegate constructor as taking two parameters, as in: delg = New ADelegate(TargetObject, PointerToMethodOfObject) However, Visual Basic is not capable of handling the second parameter, so VB supports the special syntax: delg = New ADelegate(AddressOf obj.AMethod) We point this out only to warn you about the documentation on the delegate class constructor. 7.1.2 Using a Delegate as a Function PointerThe following example illustrates the use of a delegate in the context of a callback function. In this example, we want to create a generic sort function for sorting integer arrays. The function uses the bubble sort algorithm for sorting, but it's generic in the sense that one of its parameters is a compare function that is used to do the comparison of adjacent integers. Thus, by varying the external comparison function, we can change the type of sorting ( ascending , descending, or some other method) without changing the main sort function. The compare function is a callback function, since it is a function we supply that is called by the main sort function. (In this case, the callback function is not supplying us with information, as in the font enumeration case described earlier. Instead, it is called to help the sort function do its sorting.) First, we declare a delegate. As part of the declaration of a delegate, we must specify the signature of the method that is associated with the delegate, which, in our case, is the compare function. Since the compare function should take two (adjacent) integers and return True if and only if we need to swap the integers in the bubble sort algorithm, we declare the delegate as follows: ' Returns True if need to swap Delegate Function CompareFunc(ByVal x As Integer, _ ByVal y As Integer) _ As Boolean Here are two alternative target methods for the delegate ” one for an ascending sort and one for a descending sort: Function SortAscending(ByVal x As Integer, ByVal y As Integer) As Boolean If y < x Then SortAscending = True End If End Function Function SortDescending(ByVal x As Integer, _ ByVal y As Integer) As Boolean If y > x Then SortDescending = True End If End Function Now we can define the sort routine. Note the call to the Invoke method of the delegate: Sub BubbleSort(ByVal CompareMethod As CompareFunc, _ ByVal IntArray( ) As Integer) Dim i, j, temp As Integer For i = 0 To Ubound(IntArray) For j = i + 1 To Ubound(IntArray) If CompareMethod.Invoke(IntArray(i), IntArray(j)) Then Temp = IntArray(j) IntArray(j) = IntArray(i) IntArray(i) = Temp End If Next j Next i End Sub Here is some code to exercise this example: Protected Sub Button1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Dim i As Integer Dim iArray() As Integer = New Integer( ) {6, 2, 4, 9} BubbleSort(AddressOf SortAscending, iArray) For i = 0 To 3 Debug.WriteLine(CStr(iArray(i))) Next Debug.WriteLine BubbleSort(AddressOf SortDescending, iArray) For i = 0 To 3 Debug.WriteLine(CStr(iArray(i))) Next End Sub Alternatively, we can define delegate variables instead of using the Addressof operator directly: Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim i As Integer ' Instances of the delegate type CompareFunc Dim dlgAsc As New CompareFunc(AddressOf SortAscending) Dim dlgDesc As New CompareFunc(AddressOf SortDescending) Dim iArray() As Integer = New Integer( ) {6, 2, 4, 9} BubbleSort(dlgAsc, iArray) For i = 0 To 3 Debug.WriteLine(CStr(iArray(i))) Next Debug.WriteLine("") BubbleSort(dlgDesc, iArray) For i = 0 To 3 Debug.WriteLine(CStr(iArray(i))) Next End Sub The output is, as you would expect: 2 4 6 9 9 6 4 2 |