The Helper Class Hack


In most languages, you can implement user-defined compare and swap routines using a feature called procedure variables in Pascal, function pointers in C, or, more generically, callbacks in the Windows SDK documentation. A procedure variable contains the address of a procedure. You can change the procedure by changing the address value. You can call the procedure by calling the variable. You can pass a procedure variable to a second procedure, thereby specifying the procedure that the second procedure calls to do its job.


The Windows API uses this concept freely, and now Visual Basic lets you take advantage of it with the AddressOf operator. But that applies only to the API. You can’t do the same with your Basic code.


If Basic supported procedure variables, the SortArray sub might look like this:

Sub SortArray(aTarget() As Variant, _
Optional vFirst As Variant, Optional vLast As Variant, _
procCompare As SortCompareProc, _
procSwap As SortSwapProc)
§
If procCompare(aTarget(iFirst), aTarget(iLast)) > 0 Then
procSwap aTarget(iFirst), aTarget(iLast)
End If
§

You might then call the routine with one of these statements:

SortArray aNames(), 20, 48, CompareCaseSensitive, SwapString
SortArray aThings(), 1, 100, CompareCaseInsensitive, SwapThings
SortArray aAliens(), 0, 32, CompareAliens, SwapAliens

But the gods of Basic haven’t seen fit to give us this simple feature, which even the lowly FORTRAN peons take for granted. It’s just one more feature that keeps Basic from playing with the big kids. I’m not even going to suggest that we might see procedure variables in the next version because I said that last time. Instead, we’re stuck with another hack. But this hack is at least an elegant one—unlike the two filthy hacks described in the first edition of this book. Some might even call it a feature; its performance is certainly acceptable.


What we have to do is create a polymorphic helper class that provides the required Compare and Swap methods. This might sound familiar to readers of the first edition. I demonstrated the same technique there but advised against
using it because the performance was abysmal. That’s because helper objects had to be passed as type Object, thus forcing late binding. The new Implements statement allows us to use the same technique to get early binding and good performance. Polymorphism with Implements has already been described, so we needn’t go into complete detail here.


The interface class that all sort helper classes must be compatible with looks like this:

‘ ISortHelper interface class

Function Compare(v1 As Variant, v2 As Variant) As Integer
End Function

Sub Swap(v1 As Variant, v2 As Variant)
End Sub

Sub CollectionSwap(n As Collection, _
i1 As Variant, _
i2 As Variant, _
Optional key1 As Variant, _
Optional key2 As Variant)
End Sub

Any class that implements this interface must provide all three of these members, although it can make some of them dummies. For example, a sort helper class that will never sort collections must still include CollectionSwap, but it need not provide any code for CollectionSwap. A sort helper class can also implement additional methods and properties beyond what the interface requires.


The ISortHelper interface (along with SORT.CLS) is located in the VBCore component. In addition, VBCore contains a default helper class named CSortHelper. If you look back at the sort procedures earlier in this chapter, you can see that if you don’t pass a sort helper object through the final optional parameter of SortArray, one will be instantiated for you. Furthermore, the default helper object will be initialized to a state that makes sense for most sort tasks. You can often ignore the whole helper issue and call SortArray the way you expect:

SortArray aStuff()

But if that seems too simple, or if the data you’re sorting is too complicated, you can use CSortHelper as a model. I’ve already shown the Compare and Swap methods on pages 289–291. As well, the class has a HiToLo Boolean property for setting whether to sort in ascending or descending order, and a SortMode prop­erty for indicating whether string values should be sorted by case-sensitive compares, case-insensitive compares, or by their lengths.


At this point, you might ask what all the fuss is about. This is a flexible system with good performance, so why am I moaning about the lack of procedure variables? Well, because this is bad design. Ask any object-oriented design guru. Object-oriented programming is supposed to model the world. You don’t need a sort helper to sort in the real world. In fact, an object that has no data, just methods, isn’t really an object. If there’s no data, you should be able to skip the class and use the functions directly. You might point out that the CSort­Helper does have data—the internal variables for the HiToLo and SortMode properties. But that’s a side effect. Because we have to have a class, we might as well give it data. You could just as easily have one sort helper for case-sensitive compares and another for case-insensitive—except that the class syntax of Visual Basic makes this inconvenient. But if you could use procedure parameters, you would have different kinds of Compare procedures—not one Compare procedure that varies its behavior based on property values.

And if that’s not enough, why are the Swap and Compare methods in the same class? Frequently, you use the same Swap method for many sorting problems that require a different Compare method. Why should you have to reimplement Swap when all you wanted was a new Compare? Well, they’re tied together for no reason other than convenience—because adding separate ISortHelperSwap and ISortHelperCompare interfaces would lead to a brain overload of similar classes. Much easier to put all the Compare and Swap procedures you can think up in one standard module.

The first edition of this book described another hack for faking procedure variables by taking advantage of quirks in the Visual Basic name space. I have abandoned this hack as too inflexible. The code is still used in a test in the TimeIt program. If you pre­-fer to hard-code a sorting procedure for a particular application rather than using a flexible component procedure, the old method gives slightly better performance.