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 2: Defining your own Delegate class
Starting from instance methods
Until now, we learned that the code of an object can only
execute, if its code is predefined and implemented as method in the
type of the object. Consider the following code:
S r = new S();
r.M();
When sending the massage M() to the object r of type S, we expect
that the function M() is defined and implemented as a method of S:
public class S
{
public void M()
{
Console.WriteLine("M executed");
}
}
public class C
{
static void Main()
{
S r = new S();
r.M();
}
}
|
Public Class S
Public Sub M()
Console.WriteLine("M executed")
End Sub
End Class
Public Class C
Shared Sub Main()
Dim r As New S()
r.M()
End Sub
End Class
|
Implementing a method with a delegate
Suppose now that we do not want to implement
the method M in the server class S, but instead want to implement it
in the client code. This may seem a little strange, but that is
essentially the reason why we should make use of delegates: the
delegate object will contain the implementation code of the method
we want to call. Let us be concrete, and define the method M in
class C as a non-static function:
public class C
{
public void M()
{
Console.WriteLine("M executed");
}
static void Main()
{
...
}
}
|
Public Class C
Public Sub M()
Console.WriteLine("M executed")
End Sub
Shared Sub Main()
...
End Sub
End Class
|
Next, we define a delegate class:
public delegate void D();
|
Public Delegate Sub D()
|
This arms us to redefine the S class so that it
contains a delegate reference Md of type D, instead of the signature
and definition of the method M():
|
public class
S
{
public D Md;
} |
Public Class
S
Public Md As D
End Class |
In the following test code, we define two instances, a first of
type C, and a second of type S. To the Md reference of the S object,
we assign the implementation code of the M method of a C object; we
do this by instantiating a D delegate object. The code of the Main
function in class C must now become:
static void Main()
{
C c = new C();
S s = new S();
s.Md = new D(c.M);
s.Md();
}
|
Shared Sub Main()
Dim c As New C()
Dim s As New S()
s.Md = New D(AddressOf c.M)
s.Md()
End Sub
|
One step further
Maybe you ask yourself the question: which of
both objects executes the method M, the client object c or the
server object s? The following test code will give us the answer, as
we compare both techniques. We can identify the client object from
the server object by examining the hash code: if the hash codes are
different, we can be sure that we are dealing with different
objects.
public delegate void D();
public class S
{
public D Md;
public void M()
{
Console.WriteLine("S.M executed"
+ " on object " + GetType()
+ " " + GetHashCode());
}
}
public class C
{
public void M()
{
Console.WriteLine("C.M executed"
+ " on object " + GetType()
+ " " + GetHashCode());
}
static void Main()
{
C c = new C();
S s = new S();
s.Md = new D(c.M);
s.M();
c.M();
s.Md();
}
|
Public Delegate Sub D()
Public Class S
Public Md As D
Public Sub M()
Console.WriteLine("S.M executed _
& " on object " _
& Me.GetType().ToString() _
& " " & Me.GetHashCode())
End Sub
End Class
Public Class C
Public Sub M()
Console.WriteLine("C.M executed" _
& " on object " _
& Me.GetType().ToString() _
& " " & Me.GetHashCode())
End Sub
Shared Sub Main()
Dim c As New C()
Dim s As New S()
s.Md = New D(AddressOf c.M)
s.M()
c.M()
s.Md()
End Sub
End Class
|
Running the above code will result in output
similar to:
S.M executed on object S 2
C.M executed on object C 5
C.M executed on object C 5
As you can conclude, s.Md() invokes the delegate object
new D(c.M) and this invokes the c.M method on the object
C 5. We say that the delegate object, referred to by s,
encapsulates both the instance c and the method M. Thus, invoking
the delegate s.Md will result in a call to the method M of
the instance c.
Delegates representing static or shared methods
A delegate can represent an instance function – as explained
until now- either a static or a shared function. If it represents a
static function, the delegate only encapsulates the function and not
the instance.
As example, we may alter the above code so that the delegate s.Md
will represent the static method M:
public delegate void D();
public class S
{
public D Md;
}
public class C
{
static public void M()
{
Console.WriteLine("M executed");
}
static void Main()
{
S s = new S();
s.Md = new D( M );
s.Md();
}
|
Public Delegate Sub D()
Public Class S
Public Md As D
End Class
Public Class C
Shared Public Sub M()
Console.WriteLine("M executed")
End Sub
Shared Sub Main()
Dim s As New S()
s.Md = New D(AddressOf M)
s.Md()
End Sub
End Class
|
.NET delegates compared to C++ function pointers.
The syntax used for defining delegates is not that difficult to
understand if you know about function pointers in languages as C.
Although not the same, quite often a delegate class is compared to a
C++ function pointer type. Just as a pointer type, a delegate class
defines the signature of the functions to which its references may
refer. In other terms, a delegate reference compares to a function
pointer: where the function pointer was assigned to the address of a
C++ function, there the delegate reference is assigned to a delegate
object containing a .NET function:
C++ function pointers
typedef bool (*D) (double, double); // (1) D is a function pointer type
D d; // (2) d is a function pointer
bool Compare(double a, double b) // (3) Compare is a function
{
return a > b;
}
void main()
{
d = Compare; // (4) assigns function pointer to function
cout << d(19., 62.); // (5) invokes the function pointer
}
C# delegates
public delegate bool D (double x, double y); // (1) D is a delegate class
public class Test
{
static bool Compare(double a, double b) // (3) Compare is a function
{
return a > b;
}
static void Main()
{
D d; // (2) d is a delegate reference
d = new D(Compare); // (4) assigns delegate reference
to delegate object
Console.WriteLine( d(19.0, 62.0) ); // (5) invokes the delegate object
}
}
VB.NET delegates
' (1) D is a delegate class
Public Delegate Function Del(ByVal x As Double, ByVal y As Double) As Boolean
Public Module Test
' (3) Compare is a function
Public Function Compare(ByVal a As Double, ByVal b As Double) As Boolean
Return a > b
End Function
Sub Main()
Dim d As Del ' (2) d is a delegate reference
d = New Del(AddressOf Compare) ' (4) assigns delegate reference
to delegate object
Console.WriteLine( d(19.0, 62.0) ) ' (5) invokes the delegate object
End Sub
End Module
Remarks
The delegate contains a reference to a function object, or a list
of references to function objects. A delegate is said to “cast”
methods; when it bonds to several methods it becomes multicasting.
An Example: the BinaryOp delegate
The function we want to delegate in this simple but clear example
is a simple sum:
//C#
public double Sum(double x, double y)
{
return x + y;
}
' VB.NET
Function Sum(ByVal x As Double, ByVal y As Double) As Double
Return x + y
End Function
The delegate class
Therefore, we will define a custom BinaryOp delegate class with
the following signature:
//C#
public delegate double BinaryOp(double x, double y);
' VB.NET
Public Delegate Function BinaryOp(ByVal x As Double, ByVal y As Double) As Double
As a delegate of all functions reveiving 2 double type parameters
and returning one double, BinaryOp can represent the function
Sum or any other function with the same signature. Notice
that the accessiblilty level of the delegate BinaryOp is
identical to that of the function Sum.
The delegate object
From a delegate class, a delegate object still has to be
instantiated. At the delegate’s instantiation, the function to be
represented is passed on as argument to the constructor:
//C#
BinaryOp op = new BinaryOp(Sum);
' VB.NET
Dim op As BinaryOp = New BinaryOp(AddressOf Sum)
Notice the code sense information as you type:

It tells us that the delegated function has to be of type
double (double, double), as established in the definition of the
delegate BinaryOp.
We can now execute the function Sum indirectly through its
delegate:
//C#
BinaryOp op = new BinaryOp(Calculator.Sum);
double d = op(5,6);
Console.WriteLine(d);
' VB.NET
Dim op As BinaryOp = New BinaryOp(AddressOf Calculator.Sum)
Dim d As Double = op(5,6)
Console.WriteLine(d)
Notice again as you type that the code sense recognizes the
delegate as accepting two double values as parameters.

Executing the code of this example will give you the simple
output:
11
Finally, we can now assign the delegate reference to another
binary operation, for example the Product function as defined in the
next code example:
//C#
BinaryOp op = new BinaryOp(Calculator.Product);
double d = op(5,6);
Console.WriteLine(d);
' VB.NET
Dim op As BinaryOp = New BinaryOp(AddressOf Calculator.Product)
Dim d As Double = op(5,6)
Console.WriteLine(d)
The complete example in C#

public delegate double BinaryOp(double x, double y);
public class Calculator
{
public static double Sum(double x, double y)
{
return x + y;
}
public static double Product(double x, double y)
{
return x * y;
}
public static double Quotient(double x, double y)
{
return x / y;
}
public static double Difference(double x, double y)
{
return x - y;
}
}
public class CalculatorUser
{
public static void Main()
{
BinaryOp op = new BinaryOp(Calculator.Sum);
double d = op(5,6);
Console.WriteLine(d);
}
}
The complete example in VB.NET

Public Delegate Function BinaryOp(ByVal x As Double, ByVal y As Double) As Double
Public Class Calculator
Public Shared Function Sum(ByVal x As Double, ByVal y As Double) As Double
Return x + y
End Function
Public Shared Function Product(ByVal x As Double,ByVal y As Double) As Double
Return x * y
End Function
Public Shared Function Quotient(ByVal x As Double,ByVal y As Double) As Double
Return x / y
End Function
Public Shared Function Difference(ByVal x As Double,ByVal y As Double) _
As Double
Return x - y
End Function
End Class
Module CalculatorUser
Public Sub Main()
Dim op As New BinaryOp(AddressOf Calculator.Sum)
Dim d As Double = op(5, 6)
Console.WriteLine(d)
End Sub
End Module
Remarks
In the above example, note that op isn’t a function but an object
of type BinaryOp. If you ask for example:
Console.WriteLine(op.GetType())
You get the following answer:
DelegatesTest.Calculator.BinaryOp
A delegate class is always derived from the class
System.Delegate. In our example, you can assign the BinaryOp to an
object of type System.Delegate:
//C#
BinaryOp op = new BinaryOp(Calculator.Sum);
System.Delegate d = op;
' VB.NET
Dim op As BinaryOp = New BinaryOp(AddressOf Calculator.Sum)
Dim d As System.Delegate = op
The compiler would not accept it if the two objects were not the
same type. Alternatively, you can ask:
Console.WriteLine(op.GetType().BaseType)
You would get as answer:
System.Delegate
See also:
Read more next week:
Part 6: The Scribble Application
Part 7: Visual Inheritance with Windows Forms
|