Delegates in .NET

A Primer on Creating Type-Safe References to Methods in Visual Basic .NET

Jesse Liberty

Code download available at:Delegates.exe(36 KB)

This article assumes you're familiar with Visual Basic .NET

Level of Difficulty123

SUMMARY

Delegates, new in Visual Basic .NET, are type-safe, object-oriented references to methods. By using delegates, you make your methods accessible to others and therefore more extensible. This article provides an introduction to delegates, their benefits, and how they are used.

Here the author shows you how to declare, create, and use delegates to invoke instance and shared methods at run time, and how to implement delegates as properties. He then goes on to provide examples of how delegates are used for callback functionality and explains the relationship between delegates and events. Finally, the article provides a glimpse at how delegates are implemented in intermediate language code by the Visual Basic .NET compiler.

Contents

Getting Started
Instantiating the Delegate
Instance Methods and Delegates
Shared Delegates
Multicasting
Delegates and Callback Mechanisms
Delegates and Events
Delegates Under the Covers
Delegate and Multicast Delegate
Conclusion

S uppose you are drawing up a business plan for a new computer company and you face the question of how to handle the distribution of your products. Certainly you would never consider hand-delivering each new gadget to every customer. Instead, you would delegate this responsibility to a shipping service like FedEx or UPS. There are three determinations you must make in advance: what the action is (deliver gadget), what the parameters are (customer address), and what the return value is (payment). When you actually have a package to deliver, you can then delegate the responsibility to a particular delivery service.

In Microsoft® .NET programming, you face situations in which you need to execute a particular action, but there's no way to know in advance which method you'll call upon to execute that action. For example, a button might need to call some method when it is clicked, but at the time the button is designed it's impossible to know which method it will call. You need a way to describe the kind of method you want to call. This is where delegates come in handy. In this article, I'll explore delegate use in Visual Basic® .NET.

Getting Started

A delegate is a reference type that represents a method with a specific signature and return type. You can encapsulate any matching method in that delegate. In addition, you can think of a delegate as a reference to a method.

To see how this works, let's look at a problem that delegates solve. As you will see in Figure 1, I'll create a Pair class—a simple collection that holds two objects. The Pair class creates a private array of two members called thePair:

Public Class Pair Private thePair(2) As Object

Figure 1 Using Delegates

Option Strict On Imports System Namespace DelegatesAndEvents ' a simple collection to hold two items Public Class Pair ' enumeration to indicate which value is smaller Public Enum comparison theFirst = 1 theSecond = 2 End Enum ' private array to hold the two objects Private thePair(2) As Object ' the delegate declaration Public Delegate Function WhichIsSmaller( _ ByVal obj1 As Object, ByVal obj2 As Object) As comparison ' constructor take two objects, ' added to array in order received Public Sub New( _ ByVal firstObject As Object, _ ByVal secondObject As Object) thePair(0) = firstObject thePair(1) = secondObject End Sub ' public method that orders the two objects ' by whatever criteria the objects like Public Sub Sort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ comparison.theSecond Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub ' public method that orders the two objects ' by the reverse of whatever criteria the objects like Public Sub ReverseSort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ comparison.theFirst Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub ' ask the two objects to give their string value Public Overrides Function ToString() As String Return thePair(0).ToString() & ", " & thePair(1).ToString() End Function End Class Public Class Dog Private weight As Integer Public Sub New(ByVal weight As Integer) Me.weight = weight End Sub ' dogs are ordered by weight Public Shared Function WhichDogIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Pair.comparison Dim d1 As Dog = DirectCast(o1, Dog) Dim d2 As Dog = DirectCast(o2, Dog) If d1.weight > d2.weight Then Return Pair.comparison.theSecond Else Return Pair.comparison.theFirst End If End Function Public Overrides Function ToString() As String Return weight.ToString() End Function End Class Public Class Student Private name As String Public Sub New(ByVal name As String) Me.name = name End Sub ' students are ordered alphabetically Public Shared Function WhichStudentIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Pair.comparison Dim s1 As Student = DirectCast(o1, Student) Dim s2 As Student = DirectCast(o2, Student) If String.Compare(s1.name, s2.name) < 0 Then Return Pair.comparison.theFirst Else Return Pair.comparison.theSecond End If End Function Public Overrides Function ToString() As String Return name End Function End Class Class Tester Public Sub Run() ' create two students and two dogs ' and add them to Pair objects Dim Jesse As New Student("Jesse") Dim Stacey As New Student("Stacey") Dim Milo As New Dog(65) Dim Fred As New Dog(12) Dim studentPair As New Pair(Jesse, Stacey) Dim dogPair As New Pair(Milo, Fred) Console.WriteLine("studentPair: {0}", _ studentPair.ToString()) Console.WriteLine("dogPair: {0}", _ dogPair.ToString()) ' Instantiate the delegates Dim theStudentDelegate As New _ Pair.WhichIsSmaller(AddressOf Student.WhichStudentIsSmaller) Dim theDogDelegate As New _ Pair.WhichIsSmaller(AddressOf Dog.WhichDogIsSmaller) ' sort using the delegates studentPair.Sort(theStudentDelegate) Console.WriteLine("After Sort studentPair: {0}", _ studentPair.ToString()) studentPair.ReverseSort(theStudentDelegate) Console.WriteLine("After ReverseSort studentPair: {0}", _ studentPair.ToString()) dogPair.Sort(theDogDelegate) Console.WriteLine("After Sort dogPair: {0}", _ dogPair.ToString()) dogPair.ReverseSort(theDogDelegate) Console.WriteLine("After ReverseSort dogPair: {0}", _ dogPair.ToString()) End Sub Public Shared Sub Main() Dim t As New Tester() t.Run() End Sub End Class End Namespace

Output:

studentPair: Jesse, Stacey dogPair: 65, 12 After Sort studentPair: Jesse, Stacey After ReverseSort studentPair: Stacey, Jesse After Sort dogPair: 12, 65 After ReverseSort dogPair: 65, 12

The constructor takes two objects, and adds them to this internal array in the order in which they are received:

Public Sub New(ByVal firstObject As Object, ByVal secondObject As Object) thePair(0) = firstObject thePair(1) = secondObject End Sub

Pair offers three other methods: Sort, ReverseSort, and an override of ToString. The Sort method will sort the two objects in the internal array. Of course, you do not want to make the Pair class know the test for sorting the two objects since you might store virtually any type of object in the pair (Students, Dogs, Employees, Buttons, and so on). How can Pair possibly know how to sort all these different types of objects?

The solution is to delegate the responsibility for determining which object is smallest to the objects themselves. How does Pair accomplish this? With a delegate (see Figure 1).

In the Pair class, I've defined a delegate to encapsulate (refer to) a method that compares two objects and then determines which is smaller (by whatever definition of smaller the compared class determines is appropriate):

Public Delegate Function WhichIsSmaller( _ ByVal obj1 As Object, ByVal obj2 As Object) As Comparison

This is a pretty complicated definition. Let's review this definition one piece at a time:

  • The keyword Public declares the delegate to be a public member of the Pair class.
  • The keyword Delegate signals that you are creating a delegate (rather than a method or a property).
  • The keyword Function indicates that the delegate will be used to encapsulate a function (rather than a sub).
  • The identifier WhichIsSmaller is the name of this delegate.
  • The values within the parentheses are the signature of the methods that this delegate will encapsulate. That is, this delegate may encapsulate any function that takes two objects as parameters.
  • The final keywords, As Comparison, are the return type of the methods that may be encapsulated by this delegate. Comparison is an enumeration that's defined in the Pair class:
Public Enum Comparison theFirst = 1 theSecond = 2 End Enum
  • The method that's encapsulated with this delegate must return either Comparison.theFirst or Comparison.theSecond.

In total, the statement I've just shown defines a public delegate in the Pair class named WhichIsSmaller that encapsulates functions that take two objects as parameters and that return an instance of the enumerated type Comparison.

You can encapsulate any matching method in an instance of this delegate. For example, perhaps your Pair collection will hold two Student objects, as shown here:

Public Class Student Private name As String Public Sub New(ByVal name As String) Me.name = name End Sub ' other Student methods here End Class

Your Student class must create a method that matches the WhichIsSmaller delegate. For example, you might create a method WhichStudentIsSmaller (see the code in Figure 2). This method matches the required signature; it takes two Objects as parameters and it returns a Comparison value.

Figure 2 WhichStudentIsSmaller

Public Shared Function WhichStudentIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Pair.Comparison Dim s1 As Student = DirectCast(o1, Student) Dim s2 As Student = DirectCast(o2, Student) If String.Compare(s1.name, s2.name) < 0 Then Return Pair.Comparison.theFirst Else Return Pair.Comparison.theSecond End If End Function

Because my WhichStudentIsSmaller method wants to work with the parameters as Student objects and not as the more general Object type, I'll cast the parameters to Student. This is type-safe because I'll never call this method with any other type of parameter.

Once I've cast the two objects, I can compare them. In this case, I'll compare their name values alphabetically and return the appropriate enumerated value: either Pair.Comparison.theFirst or Pair.Comparison.theSecond.

The alphabetic comparison is accomplished by calling the shared Compare method of the String class. Compare returns a negative integer value if the first string comes earlier in the alphabet than the second (s1.name comes before s2.name alphabetically). It returns a positive integer value if the second string comes earlier in the alphabet, and it returns 0 if they are equal.

Other classes can also create methods that match the WhichIsSmaller delegate. For example, I might also create a Dog class:

Public Class Dog Private weight As Integer Public Sub New(ByVal weight As Integer) Me.weight = weight End Sub ' other Dog methods here End Class

This Dog class will implement a method to compare two Dog instances based on their weight:

Public Shared Function WhichDogIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Pair.comparison Dim d1 As Dog = DirectCast(o1, Dog) Dim d2 As Dog = DirectCast(o2, Dog) If d1.weight > d2.weight Then Return Pair.Comparison.theSecond Else Return Pair.Comparison.theFirst End If End Function

The Pair class is now ready to create its Sort method. As a parameter, it will take a WhichIsSmaller delegate:

Public Sub Sort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ Comparison.theSecond Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub

The Sort method calls the delegated method through the delegate, passing in the two members of the Pair's array in return for an enumerated value. If that value is Comparison.theSecond, then it knows that the second object is smaller than the first type. It knows this without even knowing what type the two objects are! It can then reverse the two objects. If the delegated method returns Comparison.theFirst, then no swap is necessary.

Instantiating the Delegate

To test this, I can create two Student objects, like so:

Dim Jesse As New Student("Jesse") Dim Stacey As New Student("Stacey")

I then add them to a new Pair object:

Dim studentPair As New Pair(Jesse, Stacey)

Next, I can instantiate a WhichIsSmaller delegate, passing in the matching method of Student that knows how to do a comparison of the two student objects:

Dim theStudentDelegate As New _ Pair.WhichIsSmaller(AddressOf Student.WhichStudentIsSmaller)

I can now pass this delegate to the Sort method in order to sort the two students:

studentPair.Sort(theStudentDelegate)

Similarly, I could create two Dog objects, add them to a pair, instantiate a delegate based on the Dog method for comparing two Dogs, and then pass that delegate to the dog pair's sort method, as shown in the following lines:

' make two dogs Dim Milo As New Dog(65) Dim Fred As New Dog(12) ' store the two dogs in a Pair Dim dogPair As New Pair(Milo, Fred) ' instantiate a delegate Dim theDogDelegate As New _ Pair.WhichIsSmaller(AddressOf Dog.WhichDogIsSmaller) ' invoke Sort, pass in the delegate dogPair.Sort(theDogDelegate)

The Pair class has a ReverseSort that takes the same delegate as Sort. I can pass the Student and Dog delegates you just created to ReverseSort as well:

studentPair.ReverseSort(theStudentDelegate) dogPair.ReverseSort(theDogDelegate)

As you might have guessed, the logic of ReverseSort is the reverse to that of Sort. That is, you only swap the two items in the Pair if the comparison method returns a value indicating that the first object is smaller than the second. The complete source code for this example is shown in Figure 1.

Instance Methods and Delegates

In the example shown in Figure 1, you encapsulated shared methods of the Dog and Student class. I can just as easily declare the encapsulated methods to be instance methods (that is, nonshared methods) instead:

Public Comparison WhichStudentIsSmaller(o1 as Object, o2 as Object)

Although I can encapsulate this method in a delegate, I must refer to it through an instance rather than through the class, like this:

Dim theStudentDelegate As _ New Pair.WhichIsSmaller(AddressOf Jesse.WhichStudentIsSmaller)

While it is more efficient to make repeated calls to a delegate encapsulating an instance method than a static method, you do need an instance on which to invoke the method.

Shared Delegates

One disadvantage to the way the delegates are declared in Figure 1 is that the calling class (Tester) must instantiate the delegates it needs in order to sort the objects in the Pair. For example, within the Run method of Tester in Figure 1 you see the following lines of code:

Dim theStudentDelegate As New _ Pair.WhichIsSmaller(AddressOf Student.WhichStudentIsSmaller)

The Tester object is required to know that the method it wants is WhichStudentIsSmaller, but this should be an implementation detail of the Student class, invisible to the Tester object.

You can solve this problem by making the delegate instance a shared member of the Student class. This way, rather than knowing which method will handle the sort for the Student (or the Dog), the Tester class need only know that the Student class has a shared delegate named, for example, OrderStudents. The run method can then be rewritten like this:

studentPair.Sort(Student.OrderStudents)

Within the student class, the shared delegate is declared as follows:

Public Shared ReadOnly OrderStudents As _ New Pair.WhichIsSmaller(AddressOf Student.WhichStudentIsSmaller)

Similarly, I can declare a static delegate within the Dog class:

Public Shared ReadOnly OrderDogs As New Pair.WhichIsSmaller( _ AddressOf Dog.WhichDogIsSmaller)

The Run method of Tester can now order the Dog pair by writing:

dogPair.Sort(Dog.OrderDogs) Delegates As Properties

The problem with shared delegates is that they must be instantiated, whether or not they are ever used, as with Student.OrderStudents and Dog.OrderDogs in the previous example. These classes can be improved by changing the shared delegate fields to properties, as shown here:

Public Shared ReadOnly Property OrderStudents As Pair.WhichIsFirst Get Return New Pair.WhichIsFirst(AddressOf WhichStudentComesFirst) End Get End Property

The assignment of the delegate is unchanged:

studentPair.Sort(Student.OrderStudents)

The key difference, however, is that now the delegate is only instantiated when you actually access the property.

Multicasting

In some cases you may want to call not just one, but two or more methods through a single delegate. This is known as multicasting. You accomplish multicasting by encapsulating the various methods in delegates, and then you combine the delegates using the Delegate.Combine shared method. The Combine method takes an array of delegates as a parameter and returns a new delegate that represents the combination of all the delegates in the array.

To see how this works, in the following code, I'll create a simplistic class that declares a delegate:

Public Class MyClassWithDelegate ' the delegate declaration Public Delegate Sub StringDelegate(ByVal s As String) End Class

I can then create a class that implements a number of methods that match the StringDelegate:

Public Class MyImplementingClass Public Shared Sub WriteString(ByVal s As String) Console.WriteLine("Writing string {0}", s) End Sub Public Shared Sub LogString(ByVal s As String) Console.WriteLine("Logging string {0}", s) End Sub Public Shared Sub TransmitString(ByVal s As String) Console.WriteLine("Transmitting string {0}", s) End Sub End Class

Within the Run method of the Tester class, I'll instantiate three StringDelegate objects:

Dim Writer, Logger, Transmitter As MyClassWithDelegate.StringDelegate

I instantiate these delegates by passing in the address of the methods I want to encapsulate, like this:

Writer = New MyClassWithDelegate.StringDelegate( _ AddressOf MyImplementingClass.WriteString) Logger = New MyClassWithDelegate.StringDelegate( _ AddressOf MyImplementingClass.LogString) Transmitter = New MyClassWithDelegate.StringDelegate( _ AddressOf MyImplementingClass.TransmitString)

Next, I instantiate a multicast delegate that I'll use to combine the three other delegates:

Dim myMulticastDelegate As MyClassWithDelegate.StringDelegate

I create an array of the first two delegates:

Dim arr() As MyClassWithDelegate.StringDelegate = {Writer, Logger}

Then I use that array to instantiate the multicast delegate:

myMulticastDelegate = _ DirectCast(System.Delegate.Combine(arr), _ MyClassWithDelegate.StringDelegate)

DirectCast is used to cast the result of calling Combine to the specialized type MyClassWithDelegate.StringDelegate because Combine returns an object of the more general type Delegate. The complete code for this example can be found in the download at the link at the top of this article.

I can add the third delegate to the collection by calling the overloaded Combine method, this time passing in the existing multicast delegate and the new delegate to add:

myMulticastDelegate = _ DirectCast(System.Delegate.Combine(myMulticastDelegate, Transmitter), _ MyClassWithDelegate.StringDelegate)

I can remove just the Logger delegate by calling the static method Remove, passing in the multicast delegate and the delegate to be removed. The return value is a Delegate that's cast to a StringDelegate and assigned back to the multicast delegate, like this:

myMulticastDelegate = _ DirectCast(System.Delegate.Remove(myMulticastDelegate, Logger), _ MyClassWithDelegate.StringDelegate)

Delegates and Callback Mechanisms

There are two ways to get your laundry done. The first way is to put your laundry into the washing machine, put in a few quarters, and then wait for the machine to run. You wait, and then you wait some more. About 30 minutes later, the machine stops and you take your laundry back.

The second way to get your laundry done is to take it to the laundromat and say "Here, please clean this clothing and call me back when you are finished. Here's my cell number." The person in the laundromat does the work while you go off and do something else. When your laundry is ready, they call you and say "Your clothes are clean. Your pick-up number is 123." When you return, you give the person at the desk the number 123, and you get back your clothes. This strategy is known as a callback.

The .NET Framework supports the notion of an asynchronous callback. The idea of a callback is that you say to a method "Do this work, and call me back when you are finished." It is a simple and clean mechanism for multitasking. For instance, opening a file and reading data from it is time consuming. You'd like to tell the object responsible for reading from the file to "go get information from this file and let me know when you have it," while you go off and do something completely different.

The .NET Framework provides the FileStream class for this kind of service. This class will open and read a file for you asynchronously, and it will also call back a method that you designate when it has data from the file.

In the next example, shown in Figure 3, I open a FileStream object, passing in the name of the file, the fileMode (Open), the FileAccess flag (Read), and the FileShare mode (ReadWrite). I also pass in an integer signifying the buffer size and a Boolean indicating whether the FileStream should be opened asynchronously:

inputStream = New FileStream( _ "C:\temp\streams.txt", _ FileMode.Open, _ FileAccess.Read, _ FileShare.ReadWrite, _ 1024, _ True)

Figure 3 Performing File Operations

Imports System.IO Imports System.Text Public Class AsynchIOTester Private inputStream As Stream ' delegated method Private myCallBack As AsyncCallback ' buffer to hold the read data Private buffer() As Byte ' the size of the buffer Private Const BufferSize As Integer = 256 ' constructor Sub New() ' open the input stream inputStream = New FileStream( _ "C:\temp\streams.txt", _ FileMode.Open, _ FileAccess.Read, _ FileShare.ReadWrite, _ 1024, _ True) ' allocate a buffer Buffer = New Byte(BufferSize) {} ' assign the call back myCallBack = AddressOf OnCompletedRead End Sub 'New Public Shared Sub Main() ' create an instance of AsynchIOTester ' which invokes the constructor Dim theApp As New AsynchIOTester() ' call the instance method theApp.Run() End Sub 'Main Sub Run() inputStream.BeginRead( _ Buffer, _ 0, _ Buffer.Length, _ myCallBack, _ Nothing) Dim i As Long For i = 0 To 49999999 If i Mod 1000 = 0 Then Console.WriteLine("i: {0}", i) End If Next i End Sub 'Run ' call back method Sub OnCompletedRead(ByVal asyncResult As IAsyncResult) Dim bytesRead As Integer = inputStream.EndRead(asyncResult) ' if we got bytes, make them a string ' and display them, then start up again. ' Otherwise, we're done. If bytesRead > 0 Then Dim s As String = _ Encoding.ASCII.GetString(Buffer, 0, bytesRead) Console.WriteLine(s) inputStream.BeginRead( _ Buffer, 0, Buffer.Length, myCallBack, Nothing) End If End Sub 'OnCompletedRead End Class

In the example shown in Figure 3, the name of the file is hardwired as streams.txt in the c:\temp subdirectory. In production code, you will probably ask the user to specify a file. If you are testing this program, you should replace this file path with the name of a text file already on your disk.

Now that I have a FileStream object, I can call its BeginRead method, which provides asynchronous reading of the file—by reading a block of text into memory while my other code does its work. You must pass in a buffer so it has a place to put your data in, along with the offset into that buffer where it should begin reading. You must also pass in the length of the buffer and tell BeginRead the method you want to call back to.

You designate the method that you want to call back to by passing in a delegate. I will create that delegate in the next example as a member of my class:

Private myCallBack As AsyncCallback

The type of the delegate was determined by the author of the FileStream class, which designated that you must pass in a delegate of type AsyncCallback. The AsyncCallback delegate is defined in the documentation as follows:

Public Delegate Sub AsyncCallback( _ ByVal asyncResult As IAsyncResult)

The AsyncCallback delegate is a subroutine (and thus returns no value) and takes as its single parameter an object that implements the IAsyncResult interface. You do not have to implement that interface yourself. All you need to do is create a method that declares a parameter of type IAsyncResult. Such an object will be passed to you by the FileStream's BeginRead method, and you'll need to return it as a token to the FileStream by calling EndRead. Here is the declaration of the method I've encapsulated in my AsyncCallback delegate:

Sub OnCompletedRead(ByVal asyncResult As IAsyncResult) ••• End Sub 'OnCompletedRead

I instantiate the delegate in the constructor to my class:

myCallBack = New AsyncCallback(AddressOf OnCompletedRead)

As an alternative, you can simply write:

myCallBack = AddressOf OnCompletedRead

The compiler will figure out that you are instantiating an AsyncCallback delegate based on the declared type of myCallBack.

I'm now ready to start the callback process. Begin in the test class's Run method by calling BeginRead:

Sub Run() inputStream.BeginRead( _ buffer, _ 0, _ buffer.Length, _ myCallBack, _ Nothing)

The first parameter is a buffer, declared in this case as a member variable, as shown here:

Private buffer() As Byte

The second parameter is the offset into that buffer. By entering 0, the data read from the disk will be written to the buffer starting at offset 0. The third parameter is the length of the buffer. The fourth parameter is the AsyncCallBack delegate you declared and instantiated earlier. The fifth and final parameter can be any object you like (or nothing) and is typically used to hold the current state of the calling object. In the case shown, you pass Nothing, a Visual Basic .NET keyword that indicates that you have no state object.

After calling BeginRead, the program can go on with its other work. In the example shown in Figure 3 that work is simulated by counting to half a million:

Dim i As Long For i = 0 To 499999 If i Mod 1000 = 0 Then Console.WriteLine("i: {0}", i) End If Next i

The FileStream will go off and open the file on your behalf. It then reads from the file and fills up your buffer. When it is ready for you to process the data, the method you encapsulated with the delegate will be called. Recall that the delegated method is called OnCompletedRead:

Sub OnCompletedRead(ByVal asyncResult As IAsyncResult) Dim bytesRead As Integer = inputStream.EndRead(asyncResult) ' if we got bytes, make them a string ' and display them, then start up again. ' Otherwise, we're finished. If bytesRead > 0 Then Dim s As String = Encoding.ASCII.GetString(buffer, 0, bytesRead) Console.WriteLine(s) inputStream.BeginRead(buffer, 0, buffer.Length, myCallBack, Nothing) End If

When the FileStream calls my method, it will pass in an instance of a class that implements the IAsyncResult interface. The first thing this method does is pass the IAsyncResult object to the FileStream's EndRead method. EndRead returns an integer indicating the number of bytes successfully read from the file. If that value is greater than 0, the buffer has data in it.

The buffer contains bytes, but you need a string to display. The buffer can be converted to a string with Encoding.ASCII.GetString—a static method that takes a buffer, an offset, and the number of bytes read and returns an ASCII string. You can then display that string to the console.

Finally, I'll call BeginRead again, passing back the buffer, the offset (again 0), the length of the buffer, and the delegate, as well as Nothing for the state object. This begins another round. Control will then return to the Run method, and the code will continue counting bytes.

I have achieved multitasking without instantiating or managing any threads on my own; I only wrote the callback mechanism and let the FileStream do the thread management.

Delegates and Events

When a user presses a button in your application, you want to be notified. When the user closes a window, changes text in a textbox, makes a choice in a listbox, and so on, you want to be notified. These happenings are called events. Windows®-based programs are event-driven, and programmers developing for Windows have been dealing with events since...well, since Windows.

The .NET environment makes events first-class objects, and implements events with delegates. Visual Basic .NET hides many of the details of implementing events, but the delegates are still there under the covers. Consider the following:

AddHandler myButton.Click, AddressOf MyButton_Click

That's really shorthand for this:

AddHandler myButton.Click, New EventHandler(AddressOf MyButton_Click)

EventHandler is the name of the implicitly defined delegate. Usually event-handling delegates in .NET are in the following form:

Public Delegate Event (sender as Object, e as EventArgs)

This delegate encapsulates a method that takes two parameters. The first, sender, represents the object that raises the event. The second parameter, e, is an object of type EventArgs or of a class derived from EventArgs. The EventArgs object will typically contain additional information that may be of use to the method handling the event. For example, you might have a clock object that fires an event every time the second changes. You might then create a ClockEventArgs class and use that to pass in the elapsed time since the clock was originally started or reset.

When you call RaiseEvent, you are calling Invoke on EventHandler (the implicitly created delegate).

Delegates Under the Covers

It turns out that the common language runtime (CLR) and the compiler are working hard together to make your delegates work. For example, in Figure 1, when I declared a delegate

Public Delegate Function WhichIsSmaller( _ ByVal obj1 As Object, ByVal obj2 As Object) As comparison

the compiler actually created a complete class definition to support this delegate, that looks more or less like the code in Figure 4.

Figure 4 Class Definition

Public Class WhichIsSmaller Inherits System.MulticastDelegate Public Sub New( _ target as object, _ pointerToMethod as int32) End Sub Public Overridable Sub Invoke( _ ByVal firstObject As Object, _ ByVal secondObject As Object) _ End Sub Public Overridable Function BeginInvoke( _ ByVal firstObject As Object, _ ByVal secondObject as Object, _ ByVal theCallback As AsyncCallback, _ ByVal theObject As Object) As AsyncCallback End Function Public Overridable Sub EndInvoke(ByVal result As AsyncCallback) End Sub

You can see this class when you examine the Pair application in ILDASM, which is shown in Figure 5. ILDASM is the intermediate language disassembler that is provided with the .NET Framework SDK as a tool for examining the intermediate language code produced by the compiler.

Figure 5 Class in ILDASM

The nested class is named for the delegate and has the access type (public) of the delegate you declared in the outer class (Pair). The class, its constructor, and its three methods are created by the compiler on your behalf. WhichIsSmaller is derived from MulticastDelegate and as such it inherits the methods and properties of its base class.

The class that was created contains a constructor that takes two parameters. The first is a reference to an object, and the second is an integer that represents the callback method. When I created the delegate, I wrote:

Dim theStudentDelegate As New _ Pair.WhichIsSmaller(AddressOf Student.WhichStudentIsSmaller)

You may be wondering, how can this single argument (the address of Student.WhichStudentIsSmaller) be matched to the two arguments to the delegate's constructor? The compiler knows that I am creating a delegate, and it parses the delegate's arguments into the object (Student) and the method (whichStudentIsSmaller), then the compiler does the conversion.

Examine the invocation of the delegated function. In the Pair class, you'll find a Sort method that takes a Delegate as an argument, as shown here:

Public Sub Sort(ByVal theDelegatedFunc As WhichIsSmaller)

Within that method, I can invoke the delegated method, passing in the parameters it expects:

theDelegatedFunc(thePair(0), thePair(1))

When the compiler sees this invocation of the delegate, it generates code like the following:

WhichIsSmaller.Invoke(thePair(0), thePair(1))

That is, the compiler calls the Invoke method on the nested delegate class. If you double-click the Sort method of the Pair object in ILDASM and then open the disassembler, you'll find the following line of code:

IL_001c: callvirt instance valuetype Pair.DelegatesAndEvents.comparison Pair.DelegatesAndEvents.Pair/WhichIsSmaller::Invoke(object,object)

Delegate and Multicast Delegate

Originally, the design guidelines were that single-cast delegates would derive from the Delegate class and could be Subs or Functions, while multicast delegates would derive from the MulticastDelegate class, and would only be Subs. This made sense because if you're invoking multiple methods, it makes no sense to return a value. During beta testing, Microsoft decided that this was too confusing, and so all delegates now derive from MulticastDelegate. A multicast delegate used for multicasting will typically encapsulate a Sub (not a function). You can encapsulate functions, but you must either ignore their returned values or call the delegate's GetInvocationList method and invoke each delegate manually.

Conclusion

If you're using Visual Basic .NET, you can pretty much ignore delegates when creating events. You use the With Events and AddHandler keywords and let Visual Basic .NET take care of the details for you. However, if you're creating and using callback mechanisms, delegates will be very important. And it always pays to know what a language is doing behind the scenes.

For related articles see:
Visual Basic .NET: New Programming Model and Language Enhancements Boost Development Power
Basic Instincts: New Features in Visual Basic .NET: Variables, Types, Arrays, and Properties
Basic Instincts: Exploiting New Language Features in Visual Basic .NET, Part 2
Basic Instincts: Programming with Events Using .NET

Jesse Libertyis the author of a dozen books on .NET and object-oriented software development. He is the president of Liberty Associates Inc. (https://www.LibertyAssociates.com), where he provides contract programming and training in .NET technology. Portions of this article were adapted from his books, Learning VB.NET and Programming Visual Basic .NET, 2nd Edition, both published by O'Reilly & Associates.