U2U Article: "Programming with delegates and events in .NET" Copyright © 2002 by U2U nv/sa, Belgium. All rights reserved.
Please address any questions or suggestions to U2U.

Programming with delegates and events in .NET

Author: Wim UYTTERSPROT, wim@u2u.be

Part 1: Introducing Delegates
Part 2: Defining your own Delegate class

Part 3: Delegates Example: The Programmable Robot
Part 4: Introducing .NET Events
Part 5: .NET Events Example: The Programmable Robot(2)

Part 1: Introducing Delegates

Delegates are introduced in .NET as key entities that are not only used in event handling, but also in asynchronous programming and multithreading. Therefore, it is important to study the delegate syntax and grammar independently from the event handling mechanism. First question to answer is -what is a delegate? In order to answer this question, we have to differentiate the delegate object from the delegate class and the delegate reference.

The delegate object

Let us start with a very simple definition:

A delegate object is an instruction object.

In other words, a delegate object is an object that contains instructions; we could compare it with some kind of program that contains several lines of instructions. The idea here is to collect several instructions in one single delegate object; once we have created a delegate object, we can give it to some server object; later on we will ask that server object to invoke the instructions in the delegate object.

In order to instantiate a delegate object, we must obtain a delegate class first.

The delegate class

A delegate class is a special kind of class that contains only the signature of one single function. Instead of defining a delegate class by means of the class keyword, we have to use a particular declaration syntax starting with the delegate keyword:

// C# delegate return_type delegate_name ( parameter_list ) ;
' VB.NET Delegate delegate_name ( parameter_list> ) [ As return_type ]

We recognize in the definition syntax of a delegate the same signature as the functions it may represent. This reveals itself clearly, when we repeat the simplified syntax of a procedure:

//C#
return_type funtion_name (parameter_list)

' VB.NET
Sub sub_name (parameter_list)
Function funtion_name (parameter_list) As return_type

Example from the .NET Framework

In the .NET Framework libraries, you will find various delegates. A good example is the TimerCallback delegate class, defined with the following declaration:

// C#
delegate void TimerCallback ( object state ) ;

' VB.NET
Delegate Sub TimerCallback ( ByVal state As Object ) 

This delegate is required for example when creating a timer instance of the class System.Threading.Timer. The Timer’s constructor requires as first argument a TimerCallback argument:

// C#
public Timer( TimerCallback callback, object state, int dueTime, int period);

' VB.NET
Public Sub( ByVal callback As TimerCallback, ByVal state As Object, _
ByVal dueTime As Integer, ByVal period As Integer)

What we want now, is a timer object that will call a function we define ourselves. This function is for example the Tik method of our Ticker class:

public class Ticker
{
  private int i = 0;

  public void Tik(Object obj)
  {
    Console.WriteLine("tik " + ++i);
  }
  public void Tak(Object obj)
  {
    Console.WriteLine("tak " + ++i);
  }
}
Public Class Ticker

  Private i As Integer = 0

  Public Sub Tik(ByVal obj As Object)
    Console.WriteLine("tik " & ++i)
  End Sub

  Public Sub Tak(ByVal obj As Object)
    Console.WriteLine("tak " & ++i)
  End Sub

End Class

The timer object will be able to call our Ticker.Tik method only if we embed the function in a delegate object of type TimerCallback. By definition the TimerCallback delegate constructor requires a function that matches the signature “void (object) target”. And that is why the Ticker.Tik function signature here above requires a parameter of type Object.

C#

As the Ticker.Tik function possesses the required signature, we can write the following code where the delegate reference tikDelegate refers to the delegate object new TimerCallBack(ticker.Tik):

Ticker ticker = new Ticker();
TimerCallback tikDelegate = new TimerCallback(ticker.Tik);

VB.NET

In order to instantiate the TimerCallback delegate class in VB.NET, we must use VB.NET’s AddressOf operator. The AddressOf operator requires a procedure name and returns a reference to the procedure:

As the Ticker.Tik function has the signature required by the TimerCallBack delegate – Sub procedure with one parameter of type Object -, we can write the following code where the delegate reference tikDelegate refers to the delegate object New TimerCallBack(AddressOf ticker.Tik):

Dim ticker As New Ticker() Dim tik
Delegate As TimerCallBack = New TimerCallback(AddressOf ticker.Tik)

With the delegate object as argument, we can now instantiate the System.Threading.Timer class. The timer object will start immediately and call the ticker.Tik method each half second:

// C#
Timer timer = new Timer(tikDelegate, null, 0, 500);

' VB.NET
Dim timer As New Timer(tikDelegate, Nothing, 0, 500)

The delegate reference

It may be trivial to you: a delegate reference is a reference of delegate type. In the above examples, we can identify the following delegate reference:

// C#
TimerCallback tikDelegate;

' VB.NET
Dim tikDelegate As TimerCallBack

As the TimerCallback class is a delegate class, we may define our delegate references of type System.Delegate, and write some extra casting code where required:

// C#
System.Delegate tikDelegate = new TimerCallback(ticker.Tik);
Timer timer = new Timer((TimerCallback)tikDelegate, null, 0, 500);

' VB.NET
Dim tikDelegate As System.Delegate = New TimerCallback(AddressOf ticker.Tik)
Dim timer As New Timer( DirectCast(tikDelegate, TimerCallback), Nothing, 0, 500)

In the examples here below, we invoke the Thread.Sleep procedure from the System.Threading namespace, and therefore we may not forget to add the directive:

// C#
using System.Threading;

' VB.NET
Imports System.Threading

Alternatively, we could have written in full:

System.Threading.Thread.Sleep( delay )

Example from the .NET Framework (complete C# code)

using System;
using System.Threading;

namespace U2U.Examples.Delegates
{

  public class Ticker
  {
    private int i = 0;

    public void Tik(Object obj)
    {
      Console.WriteLine("tik " + ++i);
    }
    public void Tak(Object obj)
    {
      Console.WriteLine("tak " + ++i);
    }
  }

  public class Test
  {
    static void Main(string[] args)
    {
      Ticker ticker = new Ticker();
      TimerCallback tikDelegate = new TimerCallback(ticker.Tik);
      new Timer(tikDelegate, null, 0, 500);

      // The main thread will now sleep for 5 seconds:
      Thread.Sleep(5000);
    }
  }
}

Example from the .NET Framework (complete VB.NET code)

Imports System.Threading

Public Class Ticker
  Private i As Integer = 0

  Public Sub Tik(ByVal obj As Object)
    i += 1
    Console.WriteLine("tik " & i)
  End Sub
  Public Sub Tak(ByVal obj As Object)
    i += 1
    Console.WriteLine("tak " & i)
  End Sub
End Class


Module Test

  Sub Main()
    Dim ticker As New Ticker()
    Dim tikDelegate As New TimerCallback(AddressOf ticker.Tik)
    Dim timer As New Timer(tikDelegate, Nothing, 0, 500)

    ' The main thread will now sleep for 5 seconds:
    Thread.Sleep(5000)
  End Sub
 
End Module

Result of C# and VB.NET code

Notice that the main thread, i.e. the Main function here, is paused for 5 seconds with a call to the Sleep method. This is necessary in order to observe about 10 ticks as outcome.

Tik 1
Tik 2
Tik 3
...
Tik 10

Combining delegate objects

As object, a delegate can be thought of as a collection of instructions, and this explains why it is possible to combine one or more delegate objects: the instructions contained in the second delegate object will be appended to the instructions of the first object. Notice also that combining two delegates results in one single delegate, similar as combining to chains results into one single chain.

Ticker class example

The Ticker class in the previous example defines two methods, namely Tik and Tak, that have signatures accepted by the TimerCallback delegate. Therefore, we can create two delegate objects referred by tikDelegate and takDelegate:

// C#
Ticker ticker = new Ticker();
TimerCallback tikDelegate = new TimerCallback(ticker.Tik);
TimerCallback takDelegate = new TimerCallback(ticker.Tak);

' VB.NET
Dim ticker As New Ticker()
Dim tikDelegate As New TimerCallback(AddressOf ticker.Tik)
Dim takDelegate As New TimerCallback(AddressOf ticker.Tak)

Next, we define a delegate reference of type TimerCallback and assign it to null or Nothing:

// C#
TimerCallback timerDelegate = null;
 
' VB.NET
Dim timerDelegate As System.Delegate = Nothing

This reference will refer to a new delegate object that is the combination of the tikDelegate and takDelegate objects. In C# we can use the overloaded += operator, in VB.NET we can only work with the shared Delegate.Combine method:

// C#
timerDelegate += tikDelegate;
timerDelegate += takDelegate;

' VB.NET
timerDelegate = System.Delegate.Combine(tikDelegate, takDelegate)

Finally, we instantiate a new timer object that will invoke the timerDelegate every half second:

// C#
new Timer(timerDelegate, null, 0, 500);

' VB.NET
Dim timer As New Timer(DirectCast(timerDelegate, TimerCallback), Nothing, 0, 500)

The result of this code will be:

Tik 1
Tak 2
Tik 3
Tak 4
...

Combining an array of delegates

We can also combine delegates with the help of an array construction. Therefore, we must first create an array of delegates:

// C#
TimerCallback[] delegates = {tikDelegate, takDelegate, toeDelegate};
 
' VB.NET
Dim delegates As TimerCallback() = {tikDelegate, takDelegate, toeDelegate}

Here we suppose that toeDelegate is a delegate reference to an extra ticker.Toe method, similar to ticker.Tak. The delegate array can now be combined by means of the Delegate.Combine method:

// C#
timerDelegate = (TimerCallback) TimerCallback.Combine(delegates);
new Timer(timerDelegate, null, 0, 500);

' VB.NET
timerDelegate = TimerCallback.Combine(delegates)
Dim timer As New Timer(DirectCast(timerDelegate, TimerCallback), Nothing, 0, 500)

Removing a delegate

It is also possible to remove a delegate from a combination of delegates. In C# we can use the overloaded -= operator, in VB.NET we can only work with the shared Delegate.Remove method:

// C#
timerDelegate += tikDelegate;
timerDelegate += takDelegate;
timerDelegate += tikDelegate;
timerDelegate -= takDelegate;
new Timer(timerDelegate, null, 0, 500);

' VB.NET
timerDelegate = System.Delegate.Combine(tikDelegate, takDelegate)
timerDelegate = System.Delegate.Combine(timerDelegate, tikDelegate)
timerDelegate = System.Delegate.Remove(timerDelegate, takDelegate)
Dim timer As New Timer(timerDelegate, Nothing, 0, 500)

The outcome of this code will be:

Tik 1
Tik 2
Tik 3
...

SingleCast and MultiCast Delegates

The Delegate class is the base class for all delegate types. However, we cannot derive our own delegates from this base class, neigther from its immediate subclass, the MulticastDelegate class, neither from any other custom delegate type. Only the system and compilers can derive explicitly from those two classes. Buth do not worry, both C# and VB.NET implement a delegate keyword, and their compilers are able to derive from the MulticastDelegate class. Delegates come into two flavors: singlecast and multicast.

A multicast delegate object owns an invocation list, i.e. an ordered set of delegates. If we invoke a delegate object, that delegate object will invoke all delegates in its invocation list in the order in which they appear. Duplicate objects are invoked once for each time they appear in the invocation list. It may be important to know that delegates are immutable; once a delegate is instantiated, the delegate neither its invocation list can alter. If we combine two delegates with the += or the Combine method, a new delegate is created.

At the other hand, a singlecast delegate owns an invocationlist; this list only contains one delegate, namely the owner of the invocationlist.

(If you have it difficult to understand this, compare a delegate with a chain: a chain is a chain node that owns a list with chain nodes!)

When the delegate’s signature includes a return value (or is a function in VB.NET), then the invocation of a delegate returns the return value of the last element in the invocation list. This is meaningful for a singlecast delegate. In the multicast situation, it is better to use delegate signatures returning a void, respectively of Sub type for VB.NET.

 


See also:

Part 1: Introducing Delegates

Part 2: Defining your own Delegate class

Part 3: Delegates Example: The Programmable Robot

Part 4: Introducing .NET Events

Part 5: .NET Events Example: The Programmable Robot(2)


Read more next week:

Part 6: The Scribble Application

Part 7: Visual Inheritance with Windows Forms

Contact me Contact


Contact me Receive U2U Newsletter.
Looking for a challenging job Download Brochure On Site Training Looking for a challenging job
Favorites Favorites

Copyright © 1999-2010 by U2U