Tutorial: Delegates

cjard

Well-known member
Joined
Apr 25, 2006
Messages
7,081
Programming Experience
10+
I'm writing this in order to solidify some knowledge in my mind, and also to pass on some info I picked up about delegates. So many of the tutorials and examples on the net are confusing, and use counter intuitive examples, that it took me a good while to understand what they are for. Hopefully after reading this tutorial you'll understand delegates and be able to use them effectively


What is a Delegate?
It's a special construct that allows us to call a method without knowing what the method is called. It's a bit like a TV remote control, that lets you adjust the TV volume from the couch, without having to know where the buttons on the TV are, to change the volume.


So, picture a Delegate as an object that you can make (just like any other object) that can be attached to any Sub or Function in your program. Every time we Invoke() the delegate, it causes the attached method to run. Delegates can be combined together so that Invoking() them causes all the methods to run
You can picture this as having 10 TVs, 10 remotes and they are all taped together with a stick on all the volume buttons. When you push on the one stick, it changes the volume of all the TVs


I use Delegates in the following scenario:
I have a Class in my program that does boring utility things, inserting thousands of lines into a database. Its used by lots of forms, modules etc Each form or module that uses it might have a particular interest in my little class.. i.e. they might want to watch it in order to draw a progress bar or feed messages back to the user. In other words, i want my utility class (i'll give it a name of BulkDBLoader) to be observable while it goes about its work. Other classes would become observers of BulkDBLoader if they were interested in what it had to say. They might be, they might not be - the beauty of this is that its up to the form/module to decide how interested in BulkDBLoader it is, and BulkDBLoader can go about its work blissfully ignorant of other classes looking at it.

To do this, BulkDBLoader will expose a public delegate. Since this delegate will be used for the purposes of watching BulkDBLoader, it seems sensible to put it inside BulkDBLoader:

VB.NET:
[/COLOR]
[COLOR=black]Public Class BulkDBLoader[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  Public Delegate SomeThingInterestingHappenedDelegate(message as Object)[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]End Class[/COLOR]
[COLOR=black]

Here something else has crept in.. Remembering that a delegate is an object that is capable of being attached to any sub or function, there is a restriction: It can only be attached to a sub or function with an argument signature that is the same as the delegate's declared signature

In the TV analogy, it would be like saying "This universal TV remote can only be used with TVs that have an infrared port"

My delegate above, can only be attached to a sub or function that takes a single object as an argument. If i tried to attach this delegate to a sub or function that took two strings and an int, then the compiler would refuse to let it compile. In my case I chose to allow it be attached to any sub/function that takes an object - this is a very flexible approach as BulkDBLoader can literally pass any message it wants, to all its registered listeners.

In my observation example, i will actually write the sub/function that i attach the delegate to. This sub/function will be written into every form/module/class etc that has an interest in my BulkDBLoader, and this way each form(etc) that has an interest in BulkDBLoader can do different things when BulkDBLoader is notifying them that something interesting happened.


-

So now lets say we have a form, called ProgressForm, and it is keen to know when the BulkDBLoader has succeeding in pushing a record into the database:

VB.NET:
[/COLOR]
[COLOR=black]Public Class ProgressForm[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  '...other code[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  Public Sub CallMeWhenDBLoaderDoesSomething(messageFromDBLoader as Object)[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  'lets just put the tostring of the message into a listbox[/COLOR]
[COLOR=black]  MessagesFromLoaderListBox.Items.Add(message.ToString())[/COLOR]
[COLOR=black]

I'll use tostring, because it's bound to return something visible, and if the BulkDBLoader has sent a String object, it'll actually be a meaningful message too :) Remember, because it's an object, BulkDBLoader can send anything it likes

-

Great, so how do we attach the delegate to the Sub ?


First off, remember that the delegate is a type of object, so we are going to have to make a new one. Because it's a delegate, the compiler treats it a bit differently, in that it writes the constructor for you. The constructor takes just one parameter; the address of the sub or function (in memory) of the sub/function you want to call. Fortunately it's quite easy, and the IDE prompts you anyway:

VB.NET:
[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]Public Class ProgressForm[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  'in the form load sun[/COLOR]
[COLOR=black]  Public Sub Form_Load() [/COLOR]
[COLOR=black]  [/COLOR]
[COLOR=black]    Dim attachedToTheCallMeWhenSub as New BulkDBLoader.SomethingInterestingHappenedDelegate(AddressOf CallMeWhenDBLoaderDoesSomething)[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  End Sub[/COLOR]
[COLOR=black]

Remember, we want this form to watch the bulk loader, and we find the delegate inside the bulk loader. Ive chosen stupidly long variable names etc to help with the memory here.. After this line of code is finished we will have a Delegate that is attached to the CallMeWhenDBLoaderDoesSomething() sub. That's why i called the delegate "attachedToTheCallMeWhenSub"

now, if we were to Invoke() this delegate right now, the CallMeWhen sub would be called. Note that I could give this delegate to any other object in the program.. If that object called Invoke on this delegate object, then the CallMeWhen...() sub of ProgressForm would be run.

Nothing so far has made our BulkDBLoader watchable, but here's the kicker.. we now give this delegate we made, to BulkDBLoader itself and program BulkDBLoader so that when BulkDBLoader does something interesting, it invokes the delegate. When BulkDBLoader invokes the delegate, the CallMeWhen...() sub will be run. BulkDBLoader can pass a message too.

So, we are going to pass our delegate we made in ProgressForm, to BulkDBLoader and say "here, have a delegate!".. so BulkDBLoader is going to need somewhere to store this delegate for future use.. Hence we need a class-wide variable, to assign the delegate to:

VB.NET:
[/COLOR]
[COLOR=black]Public Class BulkDBLoader[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  Public Delegate SomeThingInterestingHappenedDelegate(message as Object)[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  'make a variable that we can assign a delegate to. initially it assigns to Nothing[/COLOR]
[COLOR=black]  Protected delegateToInvokeWhenSomethingHappens As SomethingInterestingHappenedDelegate = Nothing[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]End Class[/COLOR]
[COLOR=black]

I could have made it public so ProgressForm can assign it like:

BulkDBLoader.delegateToInvokeWhenSOmethingHappens = attachedToTheCallMeWhenSub


But we're going to introduce another concept i'll talk a bit more about later.. Multicasting delegates. It's the 10 TV remotes all taped together thing, where we make one Invoke of a delegate call multiple methods. Because it requires a bit more trick setting up we actually should do the work in a Property or Sub, rather than making the original delegateToInvokeWhenSOmethingHappens variable public

Here's that sub now. All this sub does is take a delegate in and
a) if its the first delegate to be taken in, assigns it straight off
b) if it's the second or subsequent delegate to be taken in, performs a combine operation instead (taping the TV remotes together)


VB.NET:
[/COLOR]
[COLOR=black]Public Class BulkDBLoader[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  Public Delegate SomeThingInterestingHappenedDelegate(message as Object)[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  'make a variable that we can assign a delegate to. initially it assigns to Nothing[/COLOR]
[COLOR=black]  Protected delegateToInvokeWhenSomethingHappens As SomethingInterestingHappenedDelegate = Nothing[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  Public Sub RegisterANewDelegate(delegateToRegister As SomethingInterestingHappenedDelegate)[/COLOR]
[COLOR=black]    If delegateToInvokeWhenSomethingHappens Is Nothing Then[/COLOR]
[COLOR=black]      delegateToInvokeWhenSomethingHappens = delegateToRegister[/COLOR]
[COLOR=black]    Else[/COLOR]
[COLOR=black]      delegateToInvokeWhenSomethingHappens = [Delegate].Combine(delegateToInvokeWhenSomethingHappens, delegateToRegister)[/COLOR]
[COLOR=black]    End If[/COLOR]
[COLOR=black]End Class[/COLOR]
[COLOR=black]

A little more about this sub: It’s a standard set-a-variable thing, and the logic is “If the variable is nothing then set it equal to the new one”.. that’s easy to understand. But whats that line in the Else? I’ll shorten it a bit here:
a = [Delegate].Combine(a, b)

Well, the word Delegate in VB is a keyword that shows in blue in the IDE, and its also the name of a class representing Delegates and their functions etc. The delegate class has a function called Combine() that takes two delegates, sticks them together (duct tape J sticks everything you know) and returns the stuck-together lump. We must store the stuck together result in the same way that we say myString = myString.Replace(“findText”, “replaceWith”)
And the square brackets around Delegate are just so VB knows you mean delegate the class, not delegate the keyword. If you wanted to make a variable called Event (not that you should) then you would also have to use square brackets.
In c# delegate (with a lowercase d) is the keyword and Delegate is the class. We don’t have that distinction here.. J


So now we are pretty much ready to go, there’s just two things we haven’t done. One is for ProgressForm to pass its delegate in, to register it, and the other is to litter some Invokes around BulkDBLoader as it does its work, so we can actually listen to what it has to say

VB.NET:
[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]Public Class ProgressForm[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  'in the form load sun[/COLOR]
[COLOR=black]  Public Sub Form_Load() [/COLOR]
[COLOR=black]  [/COLOR]
[COLOR=black]    Dim attachedToTheCallMeWhenSub as New BulkDBLoader.SomethingInterestingHappenedDelegate(AddressOf CallMeWhenDBLoaderDoesSomething)[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]    ‘form should get a reference of BulkDBLoader from somewhere[/COLOR]
[COLOR=black]    ‘given that most forms should be capable of making the objects they will watch[/COLOR]
[COLOR=black]    ‘I’ll do that here[/COLOR]
[COLOR=black]    Dim bdl as New BulkDBLoader[/COLOR]
[COLOR=black]    bdl.RegisterNewDelegate(attachedToTheCallMeWhenSub)[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]    bdl.DoYourWork()[/COLOR]
[COLOR=black] [/COLOR]
[COLOR=black]  End Sub[/COLOR]
[COLOR=black]

Now our BulkDBLoader instance bdl, is ready to go. Lets give another little discussion:

VB.NET:
  Public Sub DoYourWork()
 
    ‘this sub will load things into the database
    While thereAreThingsToLoad
      Try
        loadSomethingIntoTheDB() ‘we don’t care much about this sub
 
        delegateToInvokeWhenSomethingHappens.Invoke(“Success at time “ & Now())
      Catch ex As Exception
        delegateToInvokeWhenSomethingHappens.Invoke(ex)
      End Try
    End While
  End Sub

Remember our delegate calls any sub that takes an object, which is quite generic (you can make it specific if you want, for speed. Im going to use the genericness here)

VB.NET:
  Public Class ProgressForm
    Public Sub CallMeWhenSomethingHappens(message as Object)
      If TypeOf message Is String Then
        ‘it’s the Success! String, increment the success counter
        successCounter +=1
      ElseIf TypeOf message Is Exception Then
        failureCounter += 1
      End If
    End Sub
  End Class


There’s my brief intro to delegates, heres a summary:

Delegates are special objects that can be attached to subs/functions of the same signature as the delegate. Once created, delegates can be passed round anywhere, and any object that calls Invoke on the delegate will cause the sub/function that the delegate is attached to, to be be run. Invoke takes the same arguments as the sub/function that the Delegate is attached to.

The End J

PS; being a java guy I might have accidentally referred to a “method” in this doc. I meant Sub, or Function as applicable. In java and c# we only have methods. Methods that return void are like subs. Methods that return something are functions. Though im pretty good at flicking between the languages themselves, I still talk about VB in java/c# terms sometimes. Sorry if it confused J
 
Back
Top