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:

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