In your Visual Basic .NET journey, you have more than likely
encountered a well used, but little understood phenomenon, called a delegate. You
use them everyday, but might not know it. In this article we will take a look
at what a delegate is and how it will help you to develop better software.
A delegate can be defined as a type-safe function pointer. It
encapsulates the memory address of a function in your code. Whenever you
create or use an event in code, you are using a delegate. When the event is
thrown, the framework examines the delegate behind the event and then calls the
function that the delegate points to. As we will see later, delegates can be
combined to form groups of functions that can be called together.
Let us first take a quick look at how to define and invoke a
delegate.
First we declare our delegate in our form class:
Listing 1
Private Delegate Sub MyDelSub()
Then we use the delegate by simply declaring a variable of
the delegate and assigning the sub or function to run when called. First the
sub to be called:
Listing 2
Private Sub WriteToDebug()
Debug.WriteLine("Delegate Wrote To DebugWindow")
End Sub
You will notice also that it matches our declaration of
MyDelSub; it is a sub routine with no parameters.
And then our test code:
Listing 3
Dim del As MyDelSub
del = New MyDelSub(AddressOf WriteToDebug)
del.Invoke()
When we invoke the delegate, the WriteToDebug sub is run. Visual
Basic hides most of the implementation of delegates when you use events, which
are based off invoking a delegate. This is the equivalent of the above
delegate invoke as well.
Listing 4
Private Event MyEvent()'declare it in the class
'to use it, add a handler and raise the event.
AddHandler MyEvent, AddressOf WriteToDebug
RaiseEvent MyEvent()
If delegates stopped at this point, they would be useless
since events are less work and do the same thing. Let us get into some of the
more advanced features of delegates. We will start with multicast delegates.
Multicast delegates allow you to chain together several
functions or subs that are all called together when the delegate is invoked. For
the current iteration of the framework, you cannot designate the order that the
functions are run, only that they are all run one after another. Let us look
at the code for the multicast delegate.
First we add a new sub for our second delegate.
Listing 5
Private Sub WriteToDebug2()
Debug.WriteLine("Delegate Wrote To DebugWindow 2")
End Sub
Our declaration of the MySubDelegate stays the same, and
here is our new usage code.
Listing 6
Dim del As MyDelSub
Dim del2 As MyDelSub
Dim delAll As [Delegate]
del = New MyDelSub(AddressOf WriteToDebug)
del2 = New MyDelSub(AddressOf WriteToDebug2)
delAll = MulticastDelegate.Combine(del, del2)
delAll.DynamicInvoke(Nothing)
As we examine this code we see three delegate variables, two
for our normal delegates that call our subs and one from the combined other two
delegates. We set up our normal delegates as always, one points to
WriteToDebug and the other to WriteTodebug2. When we combine the two delegates
into our third, we utilize the static function Combine of the MulticastDelegate
class. It has two overloads, one that combines two delegates like we used and
one that takes an array of delegates. Next we invoke all the delegates with
the combined delegates' DynamicInvoke property, passing in Nothing for its
parameter. We could have also passed in an array of objects that would be used
for parameters to the invoked subs.
If you check out the declaration of the last sample, you see
another huge benefit of delegates. Notice that both del and del2 point to
different functions, but are of the same type, MyDelSub. This opens up loads
of programming potential. It allows you to point a MyDelSub variable to ANY
sub that has the same signature as itself. In our case, it is a simple sub
with no parameters. This behavior allows you to program more generically. Next
we will examine this generic behavior in detail. It works well on both sides
of the equation, either invoking a delegate from inside your class or receiving
a delegate from outside your class to work upon.
You are already familiar with the concept of having a class
invoke a delegate that originates from inside it. Anytime you handle an event
from a control you are catching a delegate that has been invoked from inside
the control. Just remember that when you register a function via handles or
addhandler, you are telling a delegate somewhere to make sure it calls your function
when it is invoked. If you have multiple functions that have handles for the
same event, you are just using a multicast delegate.
Let us take a look at some customization with delegates. You
can create classes that allow users to input delegates for certain routines. The
classes take a list of delegates to call during specific times. This can be
used to add different behaviors to your class, such as different sorting
routines. This is a more modern and slightly different version of the Visitor
design pattern as described in the GOF book. More information about the
Visitor design pattern can be found here.
Implementing the Visitor Design Pattern
To start our example, take a simple class that represents a
dog. It is very simple with only one method and one define for our delegate.
Listing 7
Public Class Bulldog
Public Delegate Sub BarkMethod()
Public Sub DoBark(ByVal BarksToRun AsBarkMethod)
BarksToRun.DynamicInvoke(Nothing)
End Sub
End Class
The delegate sub BarkMethod is what we will use to create
variables. The sub DoBark takes an instance of the delegate to run.
Now for our test code.
Listing 8
Dim bk1 As Bulldog.BarkMethod
Dim bk2 As Bulldog.BarkMethod
Dim MyDog As Bulldog
MyDog = New Bulldog
bk1 = New Bulldog.BarkMethod(AddressOf Bark1)
bk2 = New Bulldog.BarkMethod(AddressOf Bark2)
MyDog.DoBark(bk1)
MyDog.DoBark(bk2)
We create two delegate variables of the type
Bulldog.BarkBethod. We then assign each variable to a sub in our test code,
which we see here:
Listing 9
Private Sub Bark1()
Debug.WriteLine("Woof Woof")
End Sub
Private Sub Bark2()
Debug.WriteLine("Yip Yip")
End Sub
The subs simply write to the debug window. As we create and
use our dog object, we decide we want to make the dog bark by using Bark1 and
then by using Bark2. We simply pass in the correct delegate to the dog object
which calls our external subs to do the work. As you can see, this pattern
makes for very easy expansion. If we decide to make the dog talk, then we just
add a delegate variable that points to a sub that returns “Hello.”
The next step up would be the need to take multiple
delegates for the function, instead of the singular bk1 or bk2. To facilitate
this you can change the dog class to contain add and remove subs. These
control an internal delegate of what barks to run. Here is our new dog class:
Listing 10
Public Class Bulldog
Private _Barks As [Delegate]
Public Delegate Sub BarkMethod()
Public Sub DoBark()
If Not IsNothing(_Barks) Then 'check to see ifthere is anthing to Invoke
_Barks.DynamicInvoke(Nothing)
End If
End Sub
Public Sub AddBark(ByVal Bark As BarkMethod)
_Barks = MulticastDelegate.Combine(_Barks, Bark)
End Sub
Public Sub RemoveBark(ByVal Bark As BarkMethod)
_Barks = MulticastDelegate.Remove(_Barks, Bark)
End Sub
End Class
You can see that we have added a private property, _Barks,
to keep track of our delegates to call. Our AddBark and RemoveBark subs use
static functions of the MulticastDelegate to add and remove the delegate. These
functions could also be easily overloaded to take an array of BarkMethods
instead of single instances.
Here is our new test code.
Listing 11
Dim bk1 As Bulldog.BarkMethod
Dim bk2 As Bulldog.BarkMethod
Dim MyDog As Bulldog
MyDog = New Bulldog
bk1 = New Bulldog.BarkMethod(AddressOf Bark1)
bk2 = New Bulldog.BarkMethod(AddressOf Bark2)
MyDog.AddBark(bk1)
MyDog.AddBark(bk2)
MyDog.DoBark()
MyDog.RemoveBark(bk2)
MyDog.DoBark()
Our Bark1 and Bark2 subs did not change so I did not show
them. In the test code, we create our normal two delegates and then, using our
new methods, add them to our dog class. After we make the dog bark, we remove
the bk2 variable from the dog and make him bark again. The output is as
follows:
Woof Woof
Yip Yip
Woof Woof
Delegates can also be used as CallBacks. CallBacks are used
in many Windows API calls. You pass in a function pointer to the API call. When
the API call gets finished with its job, it then “calls back” to the function
via the function pointer you passed it. This allows your code to know when the
API call is done.
The framework provides an easy way to use a delegate with an
API that needs a callback. You can define and pass a delegate as a native-function
pointer several ways. Here are two. First we declare our delegate and the API
call we want to make.
Listing 12
Public Delegate Function MyDelegateCallBack( _
ByVal hwnd As Integer, ByVal lParam As Integer) As Boolean
Declare Function EnumWindows Lib "user32"(ByVal x As MyDelegateCallBack, ByVal y As Integer) As Integer
We then define the function that we want to be called by the
EnumWindows API call as it finds windows.
Listing 13
Public Function EnumOutput(ByVal hwnd As Integer,ByVal lParam As Integer) As Boolean
Console.WriteLine(hwnd)
Return True
End Function
And lastly, our test code that makes the call to EnumWindow.
Listing 14
Dim del As MyDelegateCallBack
del = New MyDelegateCallBack(AddressOf EnumOutput)
EnumWindows(del, 0)
We declare our delegate variable and then pass it to
EnumWindows, which calls it every time it finds a new window. The test code
could also be written like this:
Listing 15
EnumWindows(AddressOf EnumOutput, 0)
The shorter form creates a delegate for you and passes it to
EnumWindows.
To pass multiple delegates to CallBacks is just as easy. You
cannot use the short form of the CallBack delegate as shown above, since you
need to combine delegates. In this example the definition of EnumWindows
changes along with our test code. Here is the whole example.
Listing 16
Public Delegate Function MyDelegateCallBack( _
ByValhwnd As Integer, ByVal lParam As Integer) As Boolean
Declare FunctionEnumWindows Lib "user32" (ByVal x As [Delegate], ByVal y As Integer)As Integer
Private Sub btnButton1_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles btnButton1.Click
Dim del As MyDelegateCallBack
Dim del2 As MyDelegateCallBack
del = New MyDelegateCallBack(AddressOf EnumOutput)
del2 = New MyDelegateCallBack(AddressOfEnumOutput2)
Dim delAll As [Delegate]
delall = MulticastDelegate.Combine(del, del2)
EnumWindows(delAll, 0)
End Sub
Public Function EnumOutput(ByVal hwnd As Integer,ByVal lParam As Integer) As Boolean
Console.WriteLine(hwnd)
Return True
End Function
Public Function EnumOutput2(ByVal hwnd As Integer,ByVal lParam As Integer) As Boolean
Console.WriteLine("Found HWND")
Return True
End Function
You will notice that we use a generic delegate for the first
parameter of EnumWindows. This is necessary because we want to pass a
multicast delegate to it. We add a new function called EnumOutput2 with the
same signature as EnumOutput and create another delegate that points to it. After
creating our multicast delegate, delAll, we use it to pass to EnumWindows. The
output shows that both functions get called for each window found. Be careful
when doing this. Since EnumWindows takes a generic delegate, you are
responsible for passing in functions with the correct signature that it
expects.
Now that we know how a CallBack works, we will take a look
at how to implement one of our own and call it asynchronously. Like always, we
start by defining a delegate.
Listing 17
Public Delegate Sub MyAsyncDelegate()
Next we will show our test code and delegate functions
together and analyze them
Listing 18
Private Sub MyWorker()
System.Threading.Thread.Sleep(2000)
Debug.WriteLine("MyWorker Done!")
End Sub
Private Sub ImDone(ByVal ar As System.IAsyncResult)
Debug.WriteLine("AsyncDelegate is done")
End Sub
Private Sub btnButton1_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles btnButton1.Click
Dim del As MyAsyncDelegate
del = New MyAsyncDelegate(AddressOf MyWorker)
Dim cb As AsyncCallback = NewAsyncCallback(AddressOf ImDone)
Dim oState As Object
Dim ar As IAsyncResult = del.BeginInvoke(cb,oState)
Debug.WriteLine("After DelegateBeginInvoke")
End Sub
If you look at our two delegate functions, you will see that
the MyWorker function is the target of our normal MyAsyncDelegate. The other
function, ImDone, takes a parameter of a System.IAsyncResult. This is the
signature you need for the class AsyncCallback.
An AsyncCallback delegate allows you to call a function
asynchronously. It takes a parameter of the results. In our test code you
will see that we have created a variable named "cb" for our
AsyncCallback. This is the delegate that will get called when our function is
done.
Next we declare a variable, ar, of the IAsyncResult type and
set it equal to our normal delegate’s BeginInvoke method. This method takes a
Callback delegate and an object that represents its state. This call returns
immediately. The framework does all the work of making the call to MyWorker on
another thread for you.
When MyWorker finishes, we get our asynchronous sub’s output
of AsyncDelegate Done. If you examine the whole output, you will see that the
asynchronous call return immediately, the MyWorker sub finishes and finally the
ImDone sub is called.
After Delegate BeginInvoke
MyWorker Done!
AsyncDelegate is done
In our example, we have done all the work locally behind a
form, but one could easily define a method that took a delegate for a callback.
Instead of defining your delegate variable locally, you would use the passed-in
one as the target of the AsyncCallback object. After setting up, you would
call your internal delegate to do the work and return processing to the calling
sub. As your delegate worked, it calls the AsyncCallback whenever you decide
to notify the client.
Hopefully this article will help you understand delegates
and how Visual Basic .NET uses them. When applied correctly, they can make
your programming very generic and able to handle many different situations. Good
luck and happy coding!