Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'Data Access and Operations
22.  Delegates

In this chapter we will discuss the concept of delegates. Seen in relation to similar, previous object-oriented programming languages (such as Java and C++) this is a new topic. The inspiration comes from functional programming where functions are first class values. If x is a first class value x can be passed as parameter, x can be returned from functions, and x can be part of data structures. With the introduction of delegates, methods become first class values in C#. We will explore this "exciting world be new opportunities" in the next few sections.

22.1 Delegates in C#22.3 Multivalued delegates
22.2 Delegates that contain instance methods22.4 Lambda Expressions
 

22.1.  Delegates in C#
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

The idea of a delegate in a nutshell is as follows:

A delegate is a type the values of which consist of methods

Delegates allow us to work with variables and parameters that contain methods

Thus, a delegate in C# defines a type, in the same way as a class defines a type. A delegate reflects the signature of a set of methods, not including the method names, however. A delegate is a reference type in C#. It means that values of a delegate type are accessed via references, in the same way as an object of a class always is accessed via a reference. In particular, null is a possible delegate value.

In Program 22.1 NummericFunction is the name of a new type. This is the type of functions that accept a double and returns a double. The static method PrintTableOfFunction takes a NummericFunction f as first parameter. PrintTableOfFunction prints a table of f values within a given range [from,to] and with a given granularity, step. In the Main method we show a number of activations of PrintTableOfFunction. The first three activations generate tables of the well-known functions log, sinus, and abs. Notice that these functions belong to the NummericFunction delegate, because they are all are functions from double to double. The last activation generates a table of the method Cubic, as we have defined it in Program 22.1. It is again crucial that Cubic is a function from double to double. If Cubic had another signature, such as int -> int or double x double -> double, it would not fit with the NumericFunction delegate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;

public class Application {

  public delegate double NumericFunction(double d);   

  public static void PrintTableOfFunction(NumericFunction f, 
                                          string fname, 
                                          double from, double to, 
                                          double step){
    double d;

    for(d = from; d <= to; d += step){
     Console.WriteLine("{0,10}({1,-4:F3}) = {2}", fname, d, f(d));
    }

    Console.WriteLine();
  }

  public static double Cubic(double d){  
    return d*d*d;
  }

  public static void Main(){
    PrintTableOfFunction(Math.Log, "log", 0.1, 5, 0.1);           
    PrintTableOfFunction(Math.Sin, "sin", 0.0, 2 * Math.PI, 0.1); 
    PrintTableOfFunction(Math.Abs, "abs", -1.0, 1.0, 0.1);

    PrintTableOfFunction(Cubic, "cubic", 1.0, 5.0, 0.5);

    // Equivalent to previous:
    PrintTableOfFunction(delegate (double d){return d*d*d;},  
                         "cubic", 1.0, 5.0, 0.5);
  }
}
Program 22.1    A Delegate of simple numeric functions.

In line 31 of Program 22.1 notice the anonymous function

   delegate (double d){return d*d*d;}

The function has no name - it is anonymous. The function is equivalent with the method Cubic in line 20-22, apart from the fact that it has no name. It is noteworthy that we on the fly are able to write an expression the value of which is a method that belongs to the delegate type NumericFunction. In C#3.0 the notation for anonymous functions has been streamlined to that of lambda expressions. We will touch on this topic in Section 22.4. We outline the output of Program 22.1 in Listing 22.2 (only on web). We do not show all the output lines, however.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
       log(0.100) = -2.30258509299405
       log(0.200) = -1.6094379124341
       ...
       log(4.900) = 1.58923520511658
       log(5.000) = 1.6094379124341

       sin(0.000) = 0
       sin(0.100) = 0.0998334166468282
       ...

       sin(3.100) = 0.0415806624332892
       sin(3.200) = -0.0583741434275814
       ...
       sin(6.200) = -0.0830894028175026

       abs(-1.000) = 1
       abs(-0.900) = 0.9
       ...
       abs(1.000) = 1

     cubic(1,000) = 1
     cubic(1,500) = 3,375
     ...
     cubic(5,000) = 125
Listing 22.2    Output from the NumericFunction delegate program.

Things get even more interesting in Program 22.3. The function to watch is Compose. It accepts, as input parameters two numeric functions f and g, and it returns (another) numeric function. The idea is to return the function f o g. This is the function that returns f(g(x)) when it is given x as input.

Notice the expression Compose(Cubic, Minus3) in Main. This is a function that we pass as input to the PrintTableOfFunction, which we already have discussed. In order to examine the function Compose(Cubic, Minus3) we watch the program output in Listing 22.4. Please verify for yourself that Compose(Cubic, Minus3) is the function which subtracts 3 from its input, and thereafter calculates the cubic function on that (reduced) number.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;

public class Application {

  public delegate double NumericFunction(double d);

  public static NumericFunction Compose                           
                   (NumericFunction f, NumericFunction g){        
    return delegate(double d){return f(g(d));};                   
  }   

  public static void PrintTableOfFunction
                   (NumericFunction f, string fname, 
                    double from, double to, double step){
    double d;

    for(d = from; d <= to; d += step){
     Console.WriteLine("{0,35}({1,-4:F3}) = {2}", fname, d, f(d));
    }

    Console.WriteLine();
  }

  public static double Square(double d){
    return d*d;
  }

  public static double Cubic(double d){
    return d*d*d;
  }

  public static double Minus3(double d){
    return d-3;
  }

  public static void Main(){
    PrintTableOfFunction(Compose(Cubic, Minus3),                
                         "Cubic of Minus3", 0.0, 5.0, 1.0);     

    PrintTableOfFunction(                                       
      Compose(Square, delegate(double d){                       
                       return d > 2 ? -d : 0;}),
      "Square of if d>2 then -d else 0", 0.0, 5.0, 1.0);
  }
}
Program 22.3    The static method Compose in class Application.

1
2
3
4
5
6
7
8
9
10
11
12
13
                    Cubic of Minus3(0,000) = -27
                    Cubic of Minus3(1,000) = -8
                    Cubic of Minus3(2,000) = -1
                    Cubic of Minus3(3,000) = 0
                    Cubic of Minus3(4,000) = 1
                    Cubic of Minus3(5,000) = 8

    Square of if d>2 then -d else 0(0,000) = 0
    Square of if d>2 then -d else 0(1,000) = 0
    Square of if d>2 then -d else 0(2,000) = 0
    Square of if d>2 then -d else 0(3,000) = 9
    Square of if d>2 then -d else 0(4,000) = 16
    Square of if d>2 then -d else 0(5,000) = 25
Listing 22.4    Output from the Compose delegate program.

What we have shown above gives you the flavor of functional programming. In functional programming we often generate new functions based on existing functions, like we did with use of Compose.

Delegates make it possible to approach the functional programming style

Methods can be passed as parameters and returned as results to and from other methods

 

22.2.  Delegates that contain instance methods
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

The delegates in Section 22.1 contained static methods. Static methods are activated without a receiving object. When we put an instance method m into a delegate object, we need to find a way to provide the receiver object of m. We can, in principle, provide this object as part of the activation of the delegate, or we can aggregate it together with the method itself. In C# the latter solution has been chosen.

In Section 22.1 we show a relatively trivial class Messenger. A messenger object stores a message of type Message. Message is a delegate, shown in purple. The DoSend method calls the method in the delegate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;

public delegate void Message(string txt);  
                                           
public class Messenger{
  
  private string sender;
  private Message message;                 

  public Messenger(string sender){
   this.sender = sender;
   message = null;
  }

  public Messenger(string sender, Message aMessage){  
   this.sender = sender;
   message = aMessage;
  }

  public void DoSend(){  
   message("Message from " + sender);
  }
}
Program 22.5    A Messenger class and a Message delegate.

The class A is even more trivial. It just holds some state and an instance method called MethodA.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System;

public class A{
  
  private int state;  

  public A(int i){  
    state = i;
  }

  public void MethodA(string s){  
    Console.WriteLine("A: {0}, {1}", state, s);
  }   
}
Program 22.6    A very simple class A with an instance method MethodA.

In the class Application we create some instances of class A. The class Application is shown in Program 22.7. For now we only use one of the instances of A. We pass a2.MethodA to the Message (delegate) parameter of the Messenger constructor. With this we package both the object referred to by a2 and the method MethodA together, and it now forms part of the state of the new Message object. When the message object receives the DoSend message it activates its delegate. From the output in Listing 22.8 we see that it is in fact the instance method AMethod in the object a2 (with state equal to 2), which is called via the delegate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;

public class Application{

  public static void Main(){
    A a1 = new A(1),  
      a2 = new A(2),  
      a3 = new A(3);

    Messenger m = new Messenger("CS at AAU", a2.MethodA);  
                                                           
    m.DoSend();  
 }

}
Program 22.7    An Application class which accesses an instance method in class A.

1
A: 2, Message from CS at AAU
Listing 22.8    Output from Main of class Application.

So now we have seen that a delegate may contain an object, which consists of a receiver together with a method to be activated on the receiver.

In Section 22.3 below we will see that this is not the whole story. A delegate may in fact contain a list of such receiver/method pairs.

 

22.3.  Multivalued delegates
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

The class Messenger in Program 22.9 is an extension of class Messenger in Program 22.5. The body of the method InstallMessage shows that it is possible to add a method to a delegate. Behind the scene, a delegate is a list of methods (and, if necessary, receiver objects). The + operator has been overloaded to work on delegates. It adds a method to a delegate. Similarly, the - operator has been overloaded to remove a method from a delegate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;

public delegate void Message(string txt);  

public class Messenger{
  
  private string sender;
  private Message message;  

  public Messenger(string sender){  
   this.sender = sender;
   message = null;
  }

  public Messenger(string sender, Message aMessage){  
   this.sender = sender;
   message = aMessage;
  }

  public void InstallMessage(Message mes){  
   this.message += mes;                     
  }   

  public void UnInstallMessage(Message mes){  
   this.message -= mes;                       
  }   

  public void DoSend(){                       
   message("Message from " + sender);         
  }
}
Program 22.9    Install and UnInstall message methods in the Messenger class.

The class A, which is used in Program 22.10, can be seen in Program 22.6.

In the class Application in Program 22.10 instantiates a number of A objects and a single Messenger object. The idea is to add and remove instance methods to the Messenger object, and to activate the methods in the Messenger object via the DoSend method in line 28-31 of Program 22.9.

In line 11 of Program 22.10 we install a1.MethodA in m, which already (from the Messenger construction) contains a2.AMethod. In the program output in Listing 22.11 this is revealed in the first two output lines.

Next we install a3.AMethod twice in m. At this point in time the delegate in m contains four methods. This is seen in the middle section of Listing 22.11.

Finally, we uninstall a3.AMethod and a1.Amethod, leaving two methods in the delegate. This is shown in the last section of output in Listing 22.11.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;

public class Application{

  public static void Main(){
    A a1 = new A(1),  
      a2 = new A(2),
      a3 = new A(3);

    Messenger m = new Messenger("CS at AAU", a2.MethodA);  
    m.InstallMessage(a1.MethodA);  
    m.DoSend();  
    Console.WriteLine();

    m.InstallMessage(a3.MethodA);   
    m.InstallMessage(a3.MethodA);
    m.DoSend();
    Console.WriteLine();

    m.UnInstallMessage(a3.MethodA);  
    m.UnInstallMessage(a1.MethodA);  
    m.DoSend();
 }
}
Program 22.10    An Application class.

1
2
3
4
5
6
7
8
9
10
A: 2, Message from CS at AAU
A: 1, Message from CS at AAU

A: 2, Message from CS at AAU
A: 1, Message from CS at AAU
A: 3, Message from CS at AAU
A: 3, Message from CS at AAU

A: 2, Message from CS at AAU
A: 3, Message from CS at AAU
Listing 22.11    Output from Main of class Application.

 

22.4.  Lambda Expressions
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

A lambda expression is a value in a delegate type. Delegates where introduced in Section 22.1. The notation of lambda expression adds some extra convenience to the notation of delegates. Instead of the syntax delegate(formal-parameters){body} lambda expressions use the syntax formal-parameters => body. => is an operator in the language, see Section 6.7. It is not necessary to give the types of the formal parameters in a lambda expression. In addition, the body of a lambda expression may be an expression. In a delegate, the body must be a statement block (a command).

By the way, why is it called lambda expressions? Lambda λ is a Greek letter, like alpha α and beta α. The notion of lambda expressions come from a branch of mathematics called lambda calculus. In lambda calculus lambda expressions, such as λx. x+1, is used as a notation for functions. The particular function λx. x+1 adds one to its argument x. Lambda expression were brought into early functional programming language, most notably Lisp. Since then, "lambda expression" has been the name of those expressions which evaluate to function values.

In Program 22.12 below we make list of five equivalent functions. The first one - line 12 - uses C# delegate notation, as already introduced in Section 22.1. The last one - line 16 - is a lambda expression written as concise as possible. The three in between - line 13, 14, and 15 - illustrate the notational transition from delegate notation to lambda notation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Collections.Generic;

class Program{

  public delegate double NumericFunction(double d);   

  public static void Main(){

    List<NumericFunction> equivalentFunctions = 
       new List<NumericFunction>(){
         delegate (double d){return d*d*d;},
         (double d) => {return d*d*d;},
         (double d) => d*d*d,
         (d) => d*d*d,
         d => d*d*d
      };

    foreach(NumericFunction nf in equivalentFunctions)
       Console.WriteLine("NumericFunction({0}) = {1}", 5, nf(5));
  }

}
Program 22.12    Five equivalent functions - from anonymous method expressions to lambda expressions.

In Program 22.12 notice that we are able to organize five functions in a data structure, here a list. I line 19-12 we traverse the list of functions in a foreach control structure. Each function is bound to the local name nf, and nf(5) calls a given function on the number 5.

In Listing 22.13 (only on web) we show the output of Listing 22.13. As expected, all five calls nf(5) return the number 125.

1
2
3
4
5
NumericFunction(5) = 125
NumericFunction(5) = 125
NumericFunction(5) = 125
NumericFunction(5) = 125
NumericFunction(5) = 125
Listing 22.13    Program output.

The items below summarize lambda expressions in relation to delegates in C#:

  • The body can be a statement block or an expression

  • Uses the operator => which has low priority and is right associative

  • May involve implicit inference of parameter types

  • Lambda expressions serve as syntactic sugar for a delegate expression

Generated: Monday August 18, 2008, 16:45:36
Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'