Delegates

   


A delegate is a class and, therefore, a reference type. It is derived from the base class System.Delegate. Just like any other class, a delegate must be defined and can then be instantiated.

Note

graphics/common.gif

In OOP terminology, we distinguish between the term class as being the definition of a class you can read in the source code, whereas its instantiation that takes place during runtime is called an object. Unfortunately, delegates don't have a similar terminology. Both the delegate definition and the delegate's instantiations are referred to as delegates.


Even though delegates are derived from System.Delegate, we don't use the familiar class-derivation syntax (a colon :, as described in Chapter 16, "Inheritance Part I: Basic Concepts") for deriving and defining a new delegate (class); instead, we use the keyword delegate as in the following line:

 public delegate double Calculation(int x, int y); 

This line defines a delegate called Calculation, which can encapsulate any method that returns a double and takes two parameters both of type int.

The general syntax for defining a delegate is shown in Syntax Box 20.1.

Syntax Box 20.1 Delegate Definition

 Delegate_definition::= [<Access_modifier>] delegate <Return_type> <Identifier>  ( [<Formal_parameter_list>] ); 

where

 <Access_modifier>         ::= public         ::= protected         ::= internal         ::= private 

Notes:

Delegates are classes, so their definitions can be positioned at the same locations as conventional class definitions.

You can use the same access modifiers on delegates as on classes, and they have the same meaning.

Our delegate definition called Calculation represents a user-defined type, just like any other conventional class definition. We can use this type to declare new variables of type Calculation, as in the following line, which declares myCalculation to be of type Calculation:

 Calculation myCalculation; 

myCalculation can reference any instances of type Calculation. A new delegate instance is created in the usual way with the new keyword followed by the name of the delegate class as shown in the following line:

 myCalculation = new Calculation (<Method_name>); 

<Method_name> represents the method this delegate instance will encapsulate. This method must, as mentioned earlier, have the same return type (double in this case) and formal parameters (two ints in this case) as that specified by the delegate definition. However, the method name is irrelevant.

myCalculation can then be called like a normal method in the following way:

 result = myCalculation(15, 20); 

myCalculation delegates this call to the method it encapsulates, by invoking it (while passing the parameters along to this method) and thereby causing its statements to be executed. The return value from the encapsulated method will be passed back through myCalculation and, in this case, assigned to result. Listing 20.1 puts the pieces together into one program, which is only meant to demonstrate the mechanics of delegates, not their virtues.

Listing 20.1 Calculator.cs
01: using System; 02: 03: class Calculator 04: { 05:     public delegate double Calculation(int x, int y); 06: 07:     public static double Sum(int num1, int num2) 08:     { 09:         return num1 + num2; 10:     } 11: 12:     public static void Main() 13:     { 14:         double result; 15:         Math myMath = new Math(); 16: 17:         Calculation myCalculation = new Calculation(myMath.Average); 18:         result = myCalculation(10, 20); 19:         Console.WriteLine("Result of passing 10,20 to myCalculation: {0} ", result); 20: 21:         myCalculation = new Calculation(Sum); 22:         result = myCalculation(10, 20); 23:         Console.WriteLine("Result of passing 10,20 to myCalculation: {0} ", result); 24:     } 25: } 26: 27: class Math 28: { 29:     public double Average(int number1, int number2) 30:     { 31:         return (number1 + number2) / 2; 32:     } 33: } Result of passing 10,20 to myCalculation: 15 Result of passing 10,20 to myCalculation: 30 

Line 5 defines the delegate Calculation. The program defines two different methods called Sum (lines 7 10) and Average (lines 29 32), which both have return types and formal parameters that match those specified by the Calculation delegate and, therefore, can be encapsulated by an instance of type Calculation. Observe that the name of the methods (Sum and Average) and the name of their formal parameters do not match that of Calculation or myCalculation. These names are not taken into account when the compiler determines if a particular delegate can encapsulate a method.

Line 17 creates a new Calculation instance with the new keyword and passes as an argument the Average method of the myMath object. Thus, after line 17, myCalculation encapsulates the Average method of the myMath object. Notice that any instance method passed as an argument to the delegate constructor must be referenced with both its object name and its method name as in myMath.Average.

In line 18, myCalculation delegates the call to myMath.Average because it was assigned this method in line 17 and, therefore, returns the average of 10 and 20 as shown in the output.

Line 21 creates a new instance of Calculation, this time encapsulating Sum. Sum is static and is found within the Calculator class, so we don't need any further reference for this method than just Sum when passing it as an argument to Calculator's constructor.

Even though the call to myCalculation in line 22 is identical to line 18, it produces a different result because myCalculation encapsulates a different method at each call. Instead of finding the average, it now sums up the two numbers as specified in Sum.

Syntax Box 20.2 Delegate Instantiation

 Delegate_instantiation::=          new <Delegate_identifier> (<Method>) 

Notes:

<Method> identifies the method the delegate instance will encapsulate. Exactly one argument must always be passed to the delegate constructor.

If <Method> is an instance method of another class, it must be identified as <Object_identifier>.<Method_identifier>.

If it is a static method of another class, it must be identified as <Class_identifier>.<Method_identifier>.

Arrays of Delegates and Delegates as Arguments to Method Calls

Even though Listing 20.1 demonstrates the mechanics of delegates, it fails to demonstrate the advantages of using delegates. The following sections attempt to give you a glimpse of the possibilities provided by delegates.

Sometimes, we need to bring an item through a series of different operations, but we don't know in advance the number of operations or their sequence. For example, chemists might have an arsenal of different treatments (operations) they can apply to rinse polluted water (filtering, different chemicals, heating, cooling, and so on). A water-cleansing simulation program can perhaps help chemists find the best combination of treatments by letting them experiment and put together different treatment sequences. However, these chemists must be able to decide the sequence of the treatments during runtime by picking and choosing among the different operations, and, for each combination, see the end result. As programmers, we don't know these sequences in advance. Delegates can help us solve this type of computational problem.

Listing 20.2 implements a similar scenario to that of a water-cleansing simulation program. However, to simplify, instead of polluted water the start item is simply a number. We have three different operations that can be applied to a number: "deduct 20", "time by 2," and "add 10," but we don't know in advance in what order these operations are applied and how many operations are included (the same operation can be applied multiple times). For example, if the start number is 10 and the following operations have been chosen

 Times two        (returns 20) Times two        (returns 40) Minus twenty   (returns 20) Plus ten            (returns 30) Times two        (returns 60) 

the end result is 60.

I have called the program RocketCalculator because rocket scientists have a reputation of playing around with parts of mathematical formulas and putting them together in different ways, like the program allows us to do.

Listing 20.2 demonstrates a couple of important points our ability to create arrays of delegates (lines 7 and 13) and to write methods, which can be passed a delegate as an argument (line 16). More about this in the analysis after the sample output.

Listing 20.2 RocketCalculator.cs
 01: using System;  02:  03: delegate double DoCalculate(double num);  04:  05: class RocketCalculator  06: {  07:     private DoCalculate[] calculations;  08:     private int calculationCounter;  09:  10:     public RocketCalculator()  11:     {  12:         calculationCounter = 0;  13:         calculations = new DoCalculate[10];  14:     }  15:  16:     public void AddCalculation(DoCalculate newCalculation)  17:     {  18:         calculations[calculationCounter] = newCalculation;  19:         calculationCounter++;  20:     }  21:  22:     public double StartCalculation(double tempValue)  23:     {  24:         Console.WriteLine("Start value: {0} ", tempValue);  25:         for (int i = 0; i < calculationCounter; i++)  26:         {  27:             tempValue = calculations[i](tempValue);  28:         }  29:         return tempValue;  30:     }  31: }  32:  33: class Math  34: {  35:     public static DoCalculate DoMinusTwenty = new DoCalculate(MinusTwenty);  36:     public static DoCalculate DoTimesTwo = new DoCalculate(TimesTwo);  37:     public static DoCalculate DoPlusTen = new DoCalculate(PlusTen);  38:  39:     public static double MinusTwenty(double number)  40:     {  41:         Console.WriteLine("Minus twenty");  42:         return number - 20;  43:     }  44:  45:     public static double TimesTwo(double number)  46:     {  47:         Console.WriteLine("Times two");  48:         return number * 2;  49:     }  50:   51:     public static double PlusTen(double number)  52:     {  53:         Console.WriteLine("Plus ten");  54:         return number + 10;  55:     }  56: }  57:  58: public class Tester  59: {  60:     public static void Main()  61:     {  62:         double startValue;  63:         double endResult;  64:         string response;  65:  66:         RocketCalculator calculator = new RocketCalculator();  67:  68:         Console.Write("Enter start value: ");  69:         startValue = Convert.ToDouble(Console.ReadLine());  70:         Console.WriteLine("Create sequence of operations by repeatedly");  71:         Console.WriteLine("choosing from the following options");  72:         Console.WriteLine("M)inus twenty");  73:         Console.WriteLine("T)imes two");  74:         Console.WriteLine("P)lus ten");  75:         Console.WriteLine("When you wish to perform the calculation enter C\n");  76:  77:         do  78:         {  79:             response = Console.ReadLine().ToUpper();  80:             switch(response)  81:             {  82:                 case "M":  83:                     calculator.AddCalculation(Math.DoMinusTwenty);  84:                     Console.WriteLine("Minus twenty operation added");  85:                 break;  86:                 case "T":  87:                     calculator.AddCalculation(Math.DoTimesTwo);  88:                     Console.WriteLine("Times two operation added");  89:                 break;  90:                 case "P":  91:                     calculator.AddCalculation(Math.DoPlusTen);  92:                     Console.WriteLine("Plus ten operation added");  93:                 break;  94:                 case "C":  95:                     endResult = calculator.StartCalculation(startValue);  96:                     Console.WriteLine("End result: {0} ", endResult);  97:                 break;   98:                 default:  99:                     Console.WriteLine("Invalid choice please try again"); 100:                 break; 101:             } 102:         }  while (response != "C"); 103:     } 104: } Enter start value: 10<enter> Create sequence of operations by repeatedly choosing from the following options M)inus twenty T)imes two P)lus ten When you wish to perform the calculation enter C t Times two operation added t Times two operation added m Minus twenty operation added p Plus ten operation added t Times two operation added c Start value: 10 Times two Times two Minus twenty Plus ten Times two End result: 60 

Line 3 defines a delegate called DoCalculate. Its return type and formal parameter match those of the three methods MinusTwenty, TimesTwo, and PlusTen found in the Math class (lines 39 55). Thus, a delegate instance of DoCalculate can encapsulate any of these three methods.

We want to construct sequences of the three Math methods as described before, and we need a place to store such a sequence. To this end, we declare an array of DoCalculate elements in line 7 called calculations. It is assigned an array object of ten elements in line 13 of the RocketCalculator constructor (thus our sequence can have a maximum ten Math methods). Ten is an arbitrary number.

Each element in the calculations array can reference a DoCalculate instance that encapsulates one of the three Math class methods.

We then need the ability to add DoCalculate delegate instances that encapsulate the Math class methods we want in the sequence. This is achieved with the AddCalculation method in lines 16 20. AddCalculation's formal parameter newCalculation of type DoCalculate allows us to pass an instance of type DoCalculate to AddCalculation that encapsulates a particular Math method. newCalculation, which references this DoCalculate instance, is assigned to an element of the calculations array (line 18). The first time we call AddCalculation, the calculationCounter is equal to zero, so the first newCalculation parameter is assigned to the array element with index zero. As we add more newCalculation parameters, calculationCounter is incremented by one in line 19. This configuration allows us to fill the calculations array up with DoCalculate instances encapsulating Math methods of our choice.

We need to pass DoCalculate delegate arguments to the AddCalculation method, but how do we get hold of these instances? To facilitate this, three DoCalculate delegate instances have been created in lines 35 37 of the Math class. Each delegate instance encapsulates a different Math method. Notice that these delegates have been declared static to save us from creating an instance of the Math class every time we need access to one of these delegates.

The Tester class contains user interaction code that allows the user to create a sequence of methods. Line 66 creates a new instance of RocketCalculator called calculator. By choosing one of the letters M, T, and P, the user can assign either a Math.DoMinusTwenty (line 83) delegate instance, a Math.DoTimesTwo (line 87) delegate instance, or a Math.DoPlusTen (line 91) delegate instance to the next array element.

If you attempt to assign more than 10 calculations, the system will throw an IndexOutOfRangeException. To simplify the code, no index checks are being made and no exception handling has been implemented.

After a satisfactory combination has been assembled, the sequence can be applied on the startValue (assigned in line 69) by pressing C, which triggers the StartCalculation method of the calculator instance to be called (line 95).

calculations[i] represents a DoCalculate delegate instance that encapsulates a Math method. So, calculations[i](tempValue) (line 27) represents a call to this delegate instance. This call will be delegated to the particular Math method that it encapsulates and return the result back to be assigned to tempValue.

Multicast Delegates

The delegates presented so far only encapsulate one method each. It is also possible to define multicast delegates that can encapsulate one or more methods. When a multicast delegate is called, it invokes all the methods it encapsulates one by one. Multicast delegates are particularly useful for event handling purposes, as shown later in this chapter. For example, a button click event in an event-driven GUI program might want to invoke more than one method. Although this could also be achieved through a collection of delegates similar to the calculations array declared in line 7 of Listing 20.2, the multicast delegates are custom made for this situation, which they handle elegantly.

A delegate with the return value void is automatically a multicast delegate. In a moment, we will look at why a multicast delegate cannot return any value. The following line, taken from line 5 in Listing 20.3 defines a multicast delegate called Greeting:

 delegate void Greeting(); 

A multicast delegate is declared and can be assigned a new delegate instance, (just like a single-cast delegate) as in the following line (see line 9 of Listing 20.3):

 Greeting myGreeting = new Greeting(SayThankYou); 

which declares myGreeting to be of class Greeting and assigns it a new Greeting instance that encapsulates the SayThankYou method defined in lines 34 37 of Listing 20.3.

Line 13 similarly declares and assigns a new Greeting instance to yourGreeting:

 Greeting yourGreeting = new Greeting(SayGoodMorning); 

Two multicast delegates can be combined with the + operator and the result assigned to a multicast delegate of the same type. This new multicast delegate will encapsulate all the methods of the two multicast delegates that were added together. Thus, the following line

 Greeting ourGreeting = myGreeting + yourGreeting; 

causes ourGreeting to encapsulate all the methods encapsulated by myGreeting (one, in this case) as well as all the methods encapsulated by yourGreeting (one, in this case).

You can also use the += operator to add a multicast delegate to another multicast delegate, as in line 21

 ourGreeting += new Greeting(SayGoodnight); 

which, as usual, is the same as writing (see line 21)

 ourGreeting = ourGreeting + new Greeting(SayGoodNight); 

This causes ourGreeting to also include the SayGoodNight method among its encapsulated methods.

In a similar way, the - and -= operators can be used to remove a multicast delegate from another multicast delegate.

For example, to remove yourGreeting from ourGreeting, we can write (as in line 25):

 ourGreeting = ourGreeting - yourGreeting; 

which is similar to writing

 ourGreeting -= yourGreeting; 

Listing 20.3 uses the four operators +, +=, -, and -= on delegates and verifies their operations by printing to the console after each operator has been applied.

Listing 20.3 MulticastTester.cs
01: using System; 02: 03: class MulticastTester 04: { 05:     delegate void Greeting(); 06: 07:     public static void Main() 08:     { 09:         Greeting myGreeting = new Greeting(SayThankYou); 10:         Console.WriteLine("My single greeting:"); 11:         myGreeting(); 12: 13:         Greeting yourGreeting = new Greeting(SayGoodMorning); 14:         Console.WriteLine("\nYour single greeting:"); 15:         yourGreeting(); 16: 17:         Greeting ourGreeting = myGreeting + yourGreeting; 18:         Console.WriteLine("\nOur multicast greeting:"); 19:         ourGreeting(); 20: 21:         ourGreeting += new Greeting(SayGoodnight); 22:         Console.WriteLine("\nMulticast greeting which includes Goodnight:"); 23:         ourGreeting(); 24: 25:         ourGreeting = ourGreeting - yourGreeting; 26:         Console.WriteLine("\nMulticast greeting without your greeting:"); 27:         ourGreeting(); 28: 29:         ourGreeting -= myGreeting; 30:         Console.WriteLine("\nSingle greeting without your greeting and  my greeting: graphics/ccc.gif"); 31:         ourGreeting(); 32:     } 33: 34:     public static void SayThankYou() 35:     { 36:         Console.WriteLine("Thank you!"); 37:     } 38: 39:     public static void SayGoodMorning() 40:     { 41:         Console.WriteLine("Good morning!"); 42:     } 43: 44:     public static void SayGoodnight() 45:     { 46:         Console.WriteLine("Goodnight"); 47:     } 48: } My single greeting: Thank you! Your single greeting: Good morning! Our multicast greeting: Thank you! Good morning! Multicast greeting which includes Goodnight: Thank you! Good morning! Goodnight Multicast greeting without your greeting: Thank you! Goodnight Single greeting without your greeting and my greeting: Goodnight 

Note

graphics/common.gif

The operators +, +=, -, -= can only be used with multicast delegates, that is, delegates that return void.


Why Must Multicast Delegates be void?

graphics/common.gif

If a delegate is specified to have a return value, the method it encapsulates will also have a return value. When a delegate only encapsulates one method, this works fine because the one return value from the encapsulated method is channeled back out through the delegate's return value and back to the caller. However, if a delegate encapsulates more than one method, it is impossible for us to control where these return values end up and how they are processed. Consequently, multicast delegates cannot return any value; they must be void.



   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net