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:
Read more next week:
Part 6: The Scribble Application
Part 7: Visual Inheritance with Windows Forms
|