[Delegates]

Introduction

Tuesday, May 1, 2001

VB.NET supports delegates. They are a very cool new feature. Basicly, we're talking about references to functions wrapped up in objects, calling functions using these objects, in short, encapsulating function pointers. The overall concept, however, isn't that new: C/C++ has had function pointers like Delphi has had procedure variables for a long time. Both are similiar to delegates, but there are some notable differences in both capabilities and implementation.

Delegates are important because Events are based on them. They are used in callback tasks, and you'll meet them when dynamically creating controls at run time. They play an important role in object models, and are a building block of asynchronous programming.

Delegates are a sort of type

So what are delegates? Well first, they are a new sort of type. Read that slowly: in order to use delegates, you have to declare a type. Technically, all delegates - ultimately - inherit from Object. But they are a very special sort it. Broadly speaking, we have value types and reference types; deriving from System.Object, delegates fall into the latter category. That means, in order to use a delegate, you have to declare the delegate type, then declare a reference of that type. Then you create a new delegate instance and assign it to that reference. Creating a new delegate can be done in two ways: either use the familiar "= New ..." syntax, or use the AddressOf operator. When creating a new delegate instance, you specify a procedure that will ultimately be called.

That's similiar to dealing with objects. Here's the variant using the AddressOf operator, implicitly creating a new instance:

Delegate Sub DSaySomething()	' type declaration
Dim ss As DSaySomething		' variable declaration
ss = AddressOf SayHello		' delegate creation and assignment

You can also explicitly create the new delegate instance, based on the procedure:

Delegate Sub DSaySomething() 
Dim ss As DSaySomething
ss = New DSaySomething(AddressOf SayHello)

Creating a delegate instance this way really has the same effect as using the AddressOf operator (which returns a delegate) on the right hand side of the expression. Of course, with the new initialization syntax in VB.NET, we should be able to save a line, but note that it doesn't work (although it really is the same, demanding consistency in the rules of initialization syntax):

Delegate Sub DSaySomething()
Dim ss As New DSaySomething(AddressOf SayHello) ' error

However, you can combine declaration and assignment this way:

Dim ss As DSaySomething = AddressOf SayHello

"SayHello" must be a procedure with a matching signature (arguments and return type must match):

Sub SayHello()
	MessageBox.Show("Hello!")
End Sub

Note that the term "delegate" refers both the type you declare (a specific type of the delegate kind, similiar to the term "class"), and an instance of that type (a single instance of that type, similiar to the term "object"). This can lead to confusion. When I speak of "delegates", I'll try to make clear what I mean. It's also okay to speak of "delegate objects" or "delegate instances". In this topic, I use a capital "D" as prefix for the type name (for example, "DSaySomething" is a delegate type). For the reference-to-instance variable I choose a prefix based on the delegate type name ("ss" refers to a delegate instance of the type "DSaySomething", for example).

What is that sort of type like?

Let's consider classes. Class instances (objects) are a form of data. But that's special data, in that objects have methods that work on them. Objects are said to encapsulate data and functionality, and they can "be" or "represent" just about anything. Delegates encapsulate pointers to one or more functions, making it possible to call these functions by calling the "Invoke" method.

Delegates have one sole purpose: calling procedures. Those can be Functions or Subs. One delegate type differs from another delegate type in that the signature of the procedure to call is different (although two delegates declarations with the same signature are still considered different types). Note that ParamArrays are not legal for delegates.

Delegate Sub DUpdateStatusBar(ByVal sMsg As String)
Delegate Function DGetSetting(ByVal sSection As String, ByVal sKey As String) As Object

Note that delegate types are integrated into the type system. There is a "root" Delegate type, a MultiCastDelegate type, an EventHandler type. When you declare your own delegate type, you can assign an instance of it to a variable declared "As Delegate". You can also pass delegates as procedure arguments. As for the latter, you can use ByVal as well as ByRef (as mentioned, delegates are reference types, but they're immutable (constant), so ByRef can be important).

When you declare a delegate type, you actually derive a class from System.MulticastDelegate. If you could directly define the class (which you can't, as there is the "Delegate" syntax), the longhand of ...

Delegate Sub PrintHandler(ByVal sTextToPrint As String)

... would look like this:

Class PrintHandler
        Inherits System.MulticastDelegate

    Public Sub New(ByVal obj As IntPtr, ByVal method As IntPtr)
        ' the constructor isn't called that way
        MyBase.New(obj, method)
    End Sub

    Public Default Sub Invoke(ByVal sTextToPrint As String)
        ' there are no default methods in VB.NET, but Invoke acts like one
    End Sub

End Class

There are more methods to the class, but this might serve to illustrate the concept.

Choosing which procedure to call at runtime

A delegate instance can be used to invoke different procedures at different times:

ss = AddressOf SayHello
' do something ...
ss = AddressOf SayYourName
' do something ...
ss = AddressOf SayGoodBye

If a method is overloaded, one of the overload candidates must match the delegate's signature exactly, or an error occurs. Type checking for delegate parameters is very strict, and the upside is that overloaded methods can be used without having to worry about implicit type conversions.

If the required delegate type can be inferred from the context (for example, in an assignment statement), AddressOf will return the right delegate automatically; if it can't, the delegate needs to be created explicitely (for example, when delegate arguments are used in a procedure call, because of overloading).

The delegate instance and the procedure(s) to call

If you type the shortcut initialization statement in C# using Visual Studio, you'll find that the constructor distinguishes between the procedure on the one hand and the object the procedure is associated with on the other hand. In other words, delegates can call class instance methods (unlike the VB5/6 AddressOf mechanism). They can also call Shared (static) members, as well as global procedures (the latter means that, unlike in VB5/6, you can sink events in a module as well). Here are examples:

Delegate Function DCompare(ByVal o1 As Object, ByVal o2 As Object) As Integer
Dim cmp1, cmp2, cmp3 As DCompare
cmp1 = AddressOf comparer.CompareStrings ' instance method
cmp2 = AddressOf Class1.CompareNumbers   ' static (Shared) method
cmp3 = AddressOf Module1.CompareNoodles  ' function in module

As I'll explain below, AddressOf, used with a procedure signature, really returns a (reference to a) delegate. When used in a delegate constructor call, it causes the compiler to pass both a reference to the object that the target method operates on (or Nothing if the target method is Shared), and a pointer to that function:

Dim cmp As DCompare
cmp = New DCompare(AddressOf Me.CompareStrings)

A delegate that refers to an instance method will keep the object that contains the method reachable, preventing garbage collection. If such an object is disposed (for example, a form that's closed), take care to remove event handlers that handle events from other objects. There are two reasons for that: first, making the object eligible for garbage collection. Secondly, the actions triggered by an event handler will probably not work on an object that has been disposed.

MultiCast delegates

You can combine several delegates into a so-called MultiCast delegate (there is a special class for that). Use the Shared Combine method of the Delegate class to return a delegate that references several procedures. Here's the Combine method:

d2 = Delegate.Combine(d1, AddressOf SomeFunc)

A multicast delegate has an invocation list, which references the procedures to call. The actual implementation is a linked list of multicast delegates, but conceptually, you can ignore that. As mentioned, a delegate is immutable, so the Combine method returns a new instance. You can also remove a function from the invocation this way:

d1 = Delegate.Remove(d2, AddressOf SomeFunc)

The procedures are invoked in the order in which they were combined, but as far as I know this is unspecified (the documentation says that it depends on the order in the invocation list, but it doesn't say how the list arrives at such an order).

It is possible to add the same procedure to a multicast delegate twice. By "same procedure", we're talking about the same sub or function if it's static, or the same method on the same object if it's not static. In other words, take care when adding handlers manually. However, removing a delegate from the list works by value comparison, so even if the object passed to the remove method is not identical - but equal - to a delegate in the list, the delegate will be removed. In other words, you don't need to keep a reference to the delegate object arround just to remove it from the list. If the list contains multiple equal delegates, the first one that's equal will be removed (although I'm not sure about the "first one" part: it might also be the last one, but anyway System.Delegate.Remove call will take one delegate object that's equal to its second argument out of the invocation list).

Every delegate type you declare in VB.NET (as well as in C#) is a MultiCast delegate.

Return values, ByRef parameters and exceptions

For function multicast delegates, the last function invoked determines the return value (note that events can't be function delegates).

As ByRef parameters are implemented as pointers to the caller's variables, every procedure invoked can touch them. The result can depend on all the target procedures (for example, if they each increment the ByRef variable), but eventually, the last procedure invoked has ultimate control.

If a target throws an exception, this is passed to the place where the delegate is invoked (the exception is not wrapped like it would be if reflection were used). Pending calls to other targets are cancelled, even if the invoker handles the exception. Normally, delegate target procedures (event handlers, mostly) should not throw exceptions.

It is possible to invoke the handlers in the invocation list and thus handle any exceptions individually. This makes it possible to call back every registered target procedure, even if one of them fails (but see remarks on the DynamicInvoke method further below):

Public Function InvokeOneByOne(ByVal del As MultiCastDelegate, _
                               ParamArray ByVal args() As Object) As Object
    For Each handler As Delegate In del.GetInvocationList()
        Try
            InvokeOneByOne = handler.DynamicInvoke(args)
        Catch ex As Exception
            ' ...
        End Try
    Next handler
End Function

What's wrong with that? (About AddressOf)

So, what does the "AddressOf" keyword do? Unlike in VB6, AddressOf does not return the address of a function, if used straight in an assignment:

Dim x As DSomething
x = AddressOf Me.Hack

It returns a delegate in this case, one whose type depends on the assignment context. It's a little different when used in an explicit delegate contructor call (the constructor will actually take two pointers, as explained above, but the language does not reflect those details):

Dim x As DSomething
x = New DSomething(AddressOf Me.Hack)

Finally, AddressOf can be used in any compatible assignment context, unlike in VB5/6, where it was restricted to actual parameters in a function call, necessitating hacks like this:

' VB6 standard module
Public Function GetProcAddress(ByVal pProc As Long) As Long
	GetProcAddress = pProc
End Function
' fill BROWSEINFO structure (BrowseCallbackProc isn't documented here)
bi.lpfn = GetProcAddress(AddressOf BrowseCallbackProc)

So, in VB5/6, AddressOf returned an address. When used hackishly enough. But you did get an address. Let's not talk about VB's design principles and how a thing like an address doesn't fit in. In VB.NET, AddressOf returns a delegate (or feeds arguments to a delegate's constructor), and is no longer limited to parameter passing. So AddressOf isn't an address operator like C's "&". It might have been, sort of, in the past. But no more. It's now a "delegate operator", if you like.

Under the hood, addresses are the way of life. But Basic isn't like that. Only the operator's name is inconsistent with that. It's meant as a methaphor, as a reminder that you don't call a function and assign the result, but that you create a method pointer wrapped up in an object. The reminder definitely makes sense if the delegate is not created explicitely; it's arguably helpful in the other case, given that VB.NET doesn't always require parantheses when calling a function.

Win32 API callbacks

You can use a delegate anywhere an API expects a function pointer. The .NET runtime will pass the address of a proxy procedure to tha API, and forward the call to the delegate instance, which will call all the handlers (supplementing the "this" pointer as necessary).

The proxy procedure is necessary for a number of reasons: a function pointer refers to one function, whereas a delegate may refer to several handlers; a typical function pointer can't address an instance method, whereas a delegate can; both the delegate and target objects may be moved arround by the GC, and pointers in unmanaged code can't be updated; last but not least, there may be calling convention issues. For reasons one through three, the proxy procedure needs to exist per delegate instance, not type. Since the instance is created at runtime, the proxy is created dynamically as well.

The proxy provided by the runtime is valid only as long as the delegate instance is alive. When you call an API function that takes a delegate as one of its arguments, and create the delegate instance on the fly (inline in the procedure call), that delegate instance (and thus the proxy) is subject to garbage collection immediately after the call:

Delegate Sub StatusCallback(ByVal dw As Int32)

Class Test

    Public Sub Bad()

        ' the delegate may not be alive long enough!
        WinApi.SetStatusCallback(AddressOf HandleStatusCallback)
        WinApi.ChangeTheStatus()
        WinApi.SetStatusCallback(Nothing)

    End Sub

    Public Sub Good()

        ' the delegate will be kept alive
        Dim handler As StatusCallback = AddressOf HandleStatusCallback
        WinApi.SetStatusCallback(handler)
        WinApi.ChangeTheStatus()
        WinApi.SetStatusCallback(Nothing)

    End Sub

    Private Sub HandleStatusCallback(ByVal dw As Int32)
        ' ...
    End Sub

End Class

In other words, if you register a callback that is supposed to be used after the registration API function has returned (for example, registering a callback with InternetSetStatusCallback), you have to keep the delegate alive beyond the call. You do that by attaching a reference that has a greater scope: it must last until you deregister the callback. Often, the reference will have class instance scope.

The exception message "Function pointer not created by a delegate" indicates problems in this area.

Note that it is neither necessary nor suffiecient to keep the target object (if any) alive: as long as a delegate refers to it, it can't be collected anyway. But on the other hand, if the delegate instance is not referenced from the target object, and not referenced elsewhere, it can be collected at any point, and with it, the proxy procedure that the native code calls.

Calling native function pointers?

Can you use delegates in order to call native function pointers? This might be useful for function pointers obtained with GetProcAddress.

Sorry. While you can create a delegate instance with reflection, passing a null reference for the target object and a function pointer, this does only work for managed methods.

You can do two things:

  1. Use a C++ managed extension, and call the pointer from there.
  2. Obtain the function pointer with an appropriately declared external function (P/Invoke), so that you get a delegate created by the runtime.

Calling the procedure

You must have missed it: what use is a function calling something if we don't call the function? Well, it's about time:

Delegate Sub DUpdateText(ByVal sText As String)
Dim ut As DUpdateText
ut = AddressOf Me.WriteText
ut.Invoke("The new text.")

The Invoke method is sort of a default. You can also just use the reference as if it were a Sub or Function:

ut("The new text.")

Late-bound invocation

Another option is to invoke the delegate reflectively, using the DynamicInvoke method, passing a System.Object array of arguments. Note that in this case, only one delegate is invoked; the invocation list is not traversed (although this behaviour is not documented). This method is rarely needed.

Asynchronous invocation

For any delegate you define, the compiler emits too more methods (BeginInvoke and EndInvoke) for non-blocking invocation. This means great support for asynchronously invoking any method (note there are still synchronization issues if both threads work on the same data, as well as Windows Forms considerations), provided there's a compatible delegate type defined for it (which, of course, you can do anytime).

When I get to it, I'll provide more info on it (the docs cover it nicely; for an example, see the CWorker class in the AppCore project from the Gregor.NET series).

Events

Now let's turn the premier use of delegates in VB.NET: events. While events are still as easy to use as in VB5/6, along with the IDE lending a hand, the delegates working under the hood offer new flexibility; and the hood they're under can be opened. We'll get to what you want to do with that, shortly.

Implementation with delegates

What is an "Event"?

In .NET languages, declaring an event sets up a possibility for calling back multiple handlers. It's like a de-luxe publication of a delegate.

Technically, an event is a name in the declartion space, a hidden method to register a delegate, another hidden method for unregistering, possibly a hidden private method for raising the event (that is, execute the registered delegates), and usually a hidden private reference to the list of registered multicast delegates. Events can use any non-Function delegate type (although there are certain coding conventions as discussed below). However, any delegate type can be used outside of events as well.

Events are different from regular callbacks (which are also implemented with delegates). Events are best suited for anonymous notifications. Non-event callbacks assume a tighter coupling between the caller and the callback (as an aside, using an [adaptation] interface [or suitable base class] is a third option for what I'd rather generally deride as "the callback scenario").

Note that events may also be members of interfaces. At first, this may seem a bit confusing, but it's easiest to think of an event specification in an interface as an obligation for the implementing class to publish a given event, and the possibility for the interface user to register handlers for that event.

Think of Events as an "outgoing" part of a class, that is, an object makes a procedure call that the client handles. The object raising the events (also called "sender") is responsible for the call; it encapsulates the logic that decides if and when to make the call. The client (also called "listener") has a procedure that is the target of the call (called an event handler).

The problem is, how does the sender know which procedure to call? Or whether to call the handler at all? Now delegates take care of that: they define the signature the event handler must have, and they can be assigned a procedure to call (at runtime). It's also possible to assign several event handlers to a delegate.

Declaring and raising the event

Let's implement the KeyPress event in the CTextBox class:

Class CTextBox

	' delegate type
	Public Delegate Sub KeyPressHandler(ByVal KeyAscii As Integer)
	' reference to delegate
	Private KeyPress As KeyPressHandler

	' client calls this sub to add a KeyPress handler
	Public Sub Add_KeyPress(ByVal handler As KeyPressHandler)
		KeyPress = handler
	End Sub

	' by the way, the handler can be removed as well:
	Public Sub Remove_KeyPress()
		KeyPress = Nothing
	End Sub

	' this is an event raiser sub (can be overridden in derived classes)
	Protected Overridable Sub OnKeyPress(ByVal KeyAscii As Integer)
		If Not KeyPress Is Nothing Then 
			KeyPress.Invoke(KeyAscii)
		End If
	End Sub

End Class

Sinking the event

The event listener must assign a delegate via the Add_KeyPress method. That's it:

Class FEditor

	' reference to sender (note there's no WithEvents)
	Private m_TextBox As CTextBox

	' initializer
	Sub New()
		m_TextBox = New CTextBox
		' ... initialize form ....
		' add KeyPress handler
		m_TextBox.Add_KeyPress(AddressOf Me.HandleKeyPress)		
	End Sub
	
	' here's the event handler
	Private Sub HandleKeyPress(ByVal KeyAscii As Integer)

	End Sub

End Class

Multiple listeners: a few changes to the sender

An object can be referenced by more than one client. They all might be interested in listening to its events. Does the sender need one delegate reference for each listener? Nope:

Class CTextBox

	Public Delegate Sub KeyPressHandler(ByVal KeyAscii As Integer)
	Private KeyPress As KeyPressHandler

	' note that Remove_KeyPress now has a parameter specifying the handler to remove:
	Public Sub Add_KeyPress(ByVal handler As KeyPressHandler)
		KeyPress = DirectCast(System.Delegate.Combine(KeyPress, handler), KeyPressHandler)
	End Sub	
	Public Sub Remove_KeyPress(ByVal handler As KeyPressHandler)
		KeyPress = DirectCast(System.Delegate.Remove(KeyPress, handler), KeyPressHandler)
	End Sub

	Protected Overridable Sub OnKeyPress(ByVal KeyAscii As Integer)
		If Not KeyPress Is Nothing Then 
			KeyPress.Invoke(KeyAscii)
		End If
	End Sub

End Class

It turns out that the shared (static) method "Combine" of System.Delegate (the root type of all delegates, remember?) knows how to assign several handlers to one delegate, creating a new MultiCastDelegate instance. All handlers are added to the invocation list; handlers are (supposedly) invoked in the order of that list.

Declaring events: shortcuts

So do you have to implement Add_ and Remove_ procedures for every event? Hell no. The Event keyword takes care of that. But then, you have to raise the event with the RaiseEvent keyword:

Class CTextBox

	Public Delegate Sub KeyPressHandler(ByVal KeyAscii As Integer)
	' this takes care of adding/removing handlers:
	Public Event KeyPress As KeyPressHandler

	Protected Overridable Sub OnKeyPress(ByVal KeyAscii As Integer)
		RaiseEvent KeyPress(KeyAscii)
	End Sub
	
End Class

Note that this doesn't work with the above client-side code: see below how to change the client code to make that work.

Another shortcut

Like in classic VB, declaring an event only requires the Event keyword along with the Public modifier and an event name (possibly an argument list as well); this implicitly creates a new delegate type (which is a nested type of the class), a reference variable of that type, and the code necessary to assign/remove the handlers:

Class CTextBox

	' classic Event declaration: 
	' implicitly declare Delegate type, ref var, and hook-up procs
	Public Event KeyPress(ByVal KeyAscii As Integer)

	Protected Overridable Sub OnKeyPress(ByVal KeyAscii As Integer)
		RaiseEvent KeyPress(DirectCast(lParam, Integer))
	End Sub

End Class

Shortcuts on the client side

You don't have to call the Add_ and Remove_ procedures explicitly. In fact, if you use the Event keyword in the sender, the following is the only way to go:

Class FEditor 

	' reference to the sender
	Private m_Textbox As CTextBox 

	' add handler with AddHandler keyword
	Sub New()
		m_Textbox = New CTextBox()
		' ... initialize form ....
		AddHandler m_Textbox.KeyPress, AddressOf Me.HandleKeyPress
	End Sub

	' again, the actual handler
	Private Sub HandleKeyPress(ByVal KeyAscii As Integer)

	End Sub

End Class 

While sinking events still get a little easier (below), AddHandler (and its counterpart RemoveHandler) is a real workhorse for dynamically adding (and removing) event handlers. You'll use it when you create controls at runtime.

As easy as it used to be

Here's some very familiar code. Note that the "Handles" clause enables you to handle several events (from the same sender or from different senders) with the same sub (given that the signature is the same; but event signature have been streamlined in VB.NET).

A change over classic VB is that hooking up an event with the "Object_Event" naming pattern (without the "Handles" clause) is not supported; you need the Handles clause. However, the IDE will will insert it for you if you use those famous combo boxes in the code editor.

Class FEditor

	' object reference, implicitly create new delegate instance & hook it up
	Private WithEvents m_TextBox As CTextBox 
	
	Sub New()
		m_Textbox = New CTextBox()
		' ... initialize form ....
	End Sub
	
	' actual handler (the Handles clause is what matters)
	Private Sub m_TextBox_KeyPress(ByVal KeyAscii As Integer) Handles m_TextBox.KeyPress 

	End Sub

End Class

By the way, you could also declare the WithEvents TextBox reference with the "As New" syntax (this now creates the object):

Private WithEvents m_TextBox As New CTextBox()

Event coding conventions

Event naming patterns

The framework classes' events and related elements follow a certain naming pattern. In particular, you'll find the "OnXXX" names. But these names mean something different than in Delphi or in Javascript. Here's how you should interpret these names (the example assumes a "Fire" event):

Event handlers can have any name. The IDE uses classic VB's "Sender_Event" pattern, but you're not bound to that (see above). If the handler raises another event, maybe you should name it "OnAnotherEvent". I usually call my handler "HandleSenderEvent" (replacing parts two and three) if they are not themselves event raisers.

Event signature patterns

Now that you can define types that describe procedure signatures, it is very easy to create an event signature pattern. There is the EventArgs class, from which all classes describing event data inherit. This class has an ExtendedInfo member (type: Object), so System.EventHandler (which uses the EventArgs class) is used often, streamlining event signatures. Besides that, events pass a reference to the sender:

Public Delegate Sub EventHandler(ByVal sender As Object, _
				ByVal e As System.EventArgs)
Public Delegate Sub FireEventHandler(ByVal sender As Object, _
				ByVal fe As FireEventArgs)

Using delegates

Let's list some cases where you could use delegates: