|
|
8.1. DelegatesAn event needs some way to locate the event handler that will act when the event occurs. In some languages, the location of the handler is identified by its memory address, which is stored in a variable called a function pointer. In .NET, the location is stored instead as a delegate. In pre-.NET application development, function pointers allowed you to call a function generically when you didn't know in advance which function you were going to call. For instance, the Windows API includes a function called EnumFontFamiliesEx that provides a listing of all installed fonts. 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 API works by calling a routine in your program, once for each font. When you use EnumFontFamiliesEx, you pass it the memory address of a callback routine to use for each font; you pass this function pointer through the lpEnumFontProc parameter. The callback routine needs to include a specific parameter list signature, as defined in the API's documentation. Public Function EnumFontFamExProc(ByVal lpelfe As Long, _ ByVal lpntme As Long, ByVal FontType As Long, _ ByRef lParam As Long) As Long In VB 6, the AddressOf keyword obtains this function pointer, which you then pass to the enumeration API. The problem is that if any little thing goes wrong, your whole program will crash. That's because the function pointer is nothing more than a memory address. It can't guarantee that you put all of the ByVal and ByRef keywords in front of the right parameters, or that you even included parameters at all. In fact, there's nothing to stop you from passing any random number as the function pointer. The API doesn't care, until it crashes. This is where delegates save the day. A .NET delegate isn't just a function pointer; it's a class that includes everything you need to know to call the destination function correctly. It includes complete information about the parameters and return value, and you won't be able to compile your program until you get it all right. All delegates derive from the System.Delegate or System.MulticastDelegate classes. The former limits the delegate to a single target event handler, while the latter includes no such limit. Visual Basic uses delegates to bind events to event handlers, and sometimes it seems like a lot of work. Fortunately, Visual Studio links most events and event handlers automatically as you drag-and-drop visual elements. 8.1.1. Using a Delegate to Call a MethodTo call a method using a delegate, use the Invoke method of the delegate. To illustrate, consider a class module with a simple method. Public Class SimpleClass Public Sub CallMe(ByVal content As String) MsgBox(content) End Sub End Class Delegates are one of the many .NET types. Visual Basic includes a Delegate keyword that defines delegates. Since the goal is to call the CallMe method through a delegate, that delegate must have the same signature as the called method. This small example invokes the delegate from a click on a form, so the delegate's definition appears in the form's class code. Delegate Sub MyDelegate(ByVal s As String) Finally, in the form's Click event handler (which works via delegates, but don't think about that now), the call to CallMe is made indirectly through a delegate. Private Sub Form1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Click ' ----- Get an instance of the destination class. Dim destClass As New SimpleClass( ) Dim theDelegate As MyDelegate ' ----- Connect the delegate to its target, the CallMe method. theDelegate = New MyDelegate(AddressOf destClass.CallMe) ' ----- Make the call. theDelegate.Invoke("Display this!") End Sub It doesn't seem like much, since the code could have just called destClass.CallMe directly. But it is much, since the delegate provides generic and indirect access to the target routine for those times when the code needs something generic and indirect. MyDelegate can connect to any target routine, as long as the routine shares the same signature. The Framework Class Library takes advantage of this fact by using a common argument signature for all class events. 8.1.2. Using a Delegate as a Generic CallbackA delegate is the perfect solution when a generic callback function is needed. The following example implements a simple sorting routine. Usually, sorting routines are limited to a specific type of content, like integers or strings. That's because the sorting routine has to know how to compare the items. But if you could supply a generic "compare function," the sorting routine could sort anything. Or perhaps you need to sort the same type of data (such as an array of integers), but sort the data based on differing comparison standards. It's this second alternative that appears in the example below. The first step declares the delegate for the common comparison function. Each compare function takes two integers and returns TRue if they need to be swapped from their current order. Public Delegate Function CompareFunction(ByVal valueOne As Integer, _ ByVal valueTwo As Integer) As Boolean The two comparison functions take different approaches: one sorts in ascending order, while the other sorts in descending order. As expected, they have the same signature as the delegate. Public Function SortAscending(ByVal valueOne As Integer, _ ByVal valueTwo As Integer) As Boolean If (valueOne > valueTwo) Then Return True Else Return False End Function Public Function SortDescending(ByVal valueOne As Integer, _ ByVal valueTwo As Integer) As Boolean If (valueOne < valueTwo) Then Return True Else Return False End Function Here is the code for the sort routine. It uses the delegate's Invoke method to access the comparison function. Public Sub FlexSort(ByVal compareMethod As CompareFunction, _ ByVal dataValues( ) As Integer) ' ----- Don't tell anyone, but it's a Bubble Sort. Dim outer As Integer Dim inner As Integer Dim swap As Integer For outer = 0 To UBound(dataValues) For inner = outer + 1 To UBound(dataValues) ' ----- Make the generic delegate call here. If (compareMethod.Invoke(dataValues(outer), _ dataValues(inner)) = True) Then swap = dataValues(inner) dataValues(inner) = dataValues(outer) dataValues(outer) = swap End If Next inner Next outer End Sub The sorting code is tested from a button's Click event. Notice that the code does not specifically create a delegate. Since the FlexSort routine's first argument accepts a delegate, and since a delegate is really just a container for a matching function, the delegate gets created anyway by passing the address of a matching function. Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click ' ----- Click a button, sort some numbers. Dim counter As Integer Dim dataValues( ) As Integer = New Integer( ) {6, 2, 4, 9} ' ----- First, try it in ascending order. FlexSort(AddressOf SortAscending, dataValues) For counter = 0 To 3 Debug.WriteLine(CStr(dataValues(counter))) Next counter Debug.WriteLine("") ' ----- Next, sort them again in descending order. FlexSort(AddressOf SortDescending, dataValues) For counter = 0 To 3 Debug.WriteLine(CStr(dataValues(counter))) Next counter End Sub The output is: 2 4 6 9 9 6 4 2 |
|
|