Using Delegates in Visual Basic .NET for Runtime Flexibility

You use them every day. Learn the details of coding with Delegates.

"Delegate" is a name used to describe procedures -- that is, functions and subroutines -- in VB.NET. Just like the type Integer refers to whole numbers and the type Boolean refers to true or false, the type Delegate refers to procedures. The syntax of declaring a Delegate is almost the same. You don't use the Dim keyword but you can use a keyword like Public or Private just like you would with another declaration.

A delegate is declared by coding the name and signature of a procedure with the keyword Delegate.

Public Delegate Function myFirstDelegate(ByVal a As Integer) As Integer
Private Delegate Sub mySecondDelegate(ByRef b As String)
Public myString As String
Private myInteger As Integer
Dim mySingle As Single

Variables of 'type' myFirstDelegate are functions that take an Integer argument and return an Integer. So if you declare a function ...

Function aFunc(ByVal x As Integer) As Integer
   x = x * 10
   Return x
End Function

... then you can declare a variable of type myFirstDelegate ...

Dim myDel As myFirstDelegate
myDel = New myFirstDelegate(AddressOf aFunc)
Console.WriteLine(myDel(20))

One use of delegates allows you to control the code that actually handles events (like button clicks, change events, load events and so forth). In VB6, a subroutine with a specific name, such as Command_Click handles the click of a button, period.

But in VB.NET, the name of the subroutine isn't even important. You can rename it to be anything you like. The name that decides what code gets executed is in the Handles expression. This works using the Delegate class.

Here's a very simple "first example" of just what a Delegate does in VB.NET. Consider the standard "Say Hello" application.

Private Sub SayHello_Click( _
   ByVal sender As System.Object, _
   ByVal e As System.EventArgs) _
   Handles SayHello.Click
   lblHello.Text = "Hello"
   Me.lblHello.Font _
      = New System.Drawing.Font( _
      "Beeknees ITC", 28.0)
End Sub

Pretty simple. We've seen this before. Now add this initialization procedure to the form Load event.

Try
   If System.Environment.GetCommandLineArgs(1) _
         = "Deutsch" Then
      AddHandler cmdSayHello.Click, _
      AddressOf cmdDeutsch
      RemoveHandler cmdSayHello.Click, _
      AddressOf cmdSayHello_Click
   End If
Catch
End Try

When the initial form loads, the program checks for a command line parameter equal to "Deutsch". If one is found, a completely different subroutine is substituted as the Click event handler. (The Try/Catch block is only there to intercept a runtime exception if no command line parameter at all is present.) In a more realistic application, information about language could come from an XML configuration file, language settings in a web page, or some other source.

Before this routine can work, however, we also need a delegated subroutine. Again, in a more realistic application, this could call in a whole web page translation system.

Now, the Click event is handled by totally different code.

Private Sub cmdDeutsch( _
   ByVal sander As System.Object, _
   ByVal e As System.EventArgs)
   lblHello.Text = "Guten Tag"
   Me.lblHello.Font _
      = New System.Drawing.Font( _
      "GF Gesetz", 28.0)
End Sub

The Label now looks like this:

--------
Click Here to display the illustration
Click the Back button on your browser to return
--------

This is a fairly awkward way to do things, however. Normally, you won't be changing the code executed by event subroutines, you'll be controlling your own delegate code. The code can be rewritten to accomplish this too.

Public Class Form1
   Delegate Sub MyDelegate()
   Dim del As MyDelegate
   Private Sub SayHelloInDeutch()
      lblHello.Text = "Guten Tag"
      Me.lblHello.Font _
         = New System.Drawing.Font( _
         "GF Gesetz", 28.0)
   End Sub
   Private Sub SayHelloInEnglish()
      lblHello.Text = "Hello"
      Me.lblHello.Font _
         = New System.Drawing.Font( _
         "Beeknees ITC", 28.0)
   End Sub
   Private Sub SayHello_Click( ...
      del.Invoke()
   End Sub
   Private Sub DeutchHello_Click( ...
      del = New MyDelegate(AddressOf SayHelloInDeutch)
   End Sub
   Private Sub EnglishHello_Click( ...
      del = New MyDelegate(AddressOf SayHelloInEnglish)
   End Sub
End Class

And, just to make the point clear, you could do the same thing with events.

Public Class Form1
   Private Event MyEvent()

' The delegate subroutines are exactly the same as above

   Private Sub SayHello_Click( ...
      RaiseEvent MyEvent()
   End Sub
   Private Sub DeutchHello_Click( ...
      AddHandler MyEvent, AddressOf SayHelloInDeutch
      RemoveHandler MyEvent, AddressOf SayHelloInEnglish
   End Sub
   Private Sub EnglishHello_Click( ...
      AddHandler MyEvent, AddressOf SayHelloInEnglish
      RemoveHandler MyEvent, AddressOf SayHelloInDeutch
   End Sub
End Class

We've seen that delegates really hold the address of procedures. In fact, they can hold more than one address and when a delegate contains more than one address, it's called a multicast delegate. The list of addresses is called an invocation list. In the case of an event that calls a multicast delegate, all of the addresses in the invocation list get called.

To make that work, VB.NET provides the Combine function.

Just assign both addresses to the same delegate and combine them:

Dim myDel1 As MyDelegate = _
   New MyDelegate(AddressOf SayHelloInEnglish)
Dim myDel2 As MyDelegate = _
   New MyDelegate(AddressOf SayHelloInDeutch)
Dim myDelBoth As MyDelegate = _
   [Delegate].Combine(myDel1, myDel2)myDelBoth.Invoke()

(Note: The delegate subroutines would also have to be modified with something like ...

lblHello.Text &= vbCrLf & "Guten Tag"

... to avoid replacing the text in the label.