Chapter 12. Delegates and Events

When a head of state dies, the president of the United States typically does not have time to attend the funeral personally. Instead, he dispatches a delegate. Often this delegate is the vice president, but sometimes the VP is unavailable and the president must send someone else, such as the secretary of state or even the first lady. He does not want to “hardwire” his delegated authority to a single person; he might delegate this responsibility to anyone who is able to execute the correct international protocol.

The president defines in advance what authority will be delegated (attend the funeral), what parameters will be passed (condolences, kind words), and what value he hopes to get back (good will). He then assigns a particular person to that delegated responsibility at “runtime” as the course of his presidency progresses.

In programming, you are often faced with situations where you need to execute a particular action, but you don’t know in advance which method, or even which object, you’ll want to call upon to execute that action. For example, a button might know that it must notify some object when it is pushed, but it might not know which object or objects need to be notified. Rather than wiring the button to a particular object, you will connect the button to a delegate and then resolve that delegate to a particular method when the program executes.

In the early, dark, and primitive days of computing, a program would begin execution and then proceed through its steps until it completed. If the user was involved, the interaction was strictly controlled and limited to filling in fields.

Today’s Graphical User Interface (GUI) programming model requires a different approach, known as event-driven programming . A modern program presents the user interface and waits for the user to take an action. The user might take many different actions, such as choosing among menu selections, pushing buttons, updating text fields, clicking icons, and so forth. Each action causes an event to be raised. Other events can be raised without direct user action, such as events that correspond to timer ticks of the internal clock, email being received, file-copy operations completing, etc.

An event is the encapsulation of the idea that “something happened” to which the program must respond. Events and delegates are tightly coupled concepts because flexible event handling requires that the response to the event be dispatched to the appropriate event handler. An event handler is typically implemented in C# as a delegate.

Delegates are also used as callbacks so that one class can say to another “do this work and when you’re done, let me know.” This second usage will be covered in detail in Chapter 19. Delegates can also be used to specify methods that will only become known at runtime, a topic that will be developed in the following sections.

Delegates

In C#, delegates are first-class objects, fully supported by the language. Technically, a delegate is a reference type used to encapsulate a method with a specific signature and return type. You can encapsulate any matching method in that delegate. (In C++ and many other languages, you can accomplish this requirement with function pointers and pointers to member functions. Unlike function pointers, delegates are object-oriented and type-safe.)

A delegate is created with the delegate keyword, followed by a return type and the signature of the methods that can be delegated to it, as in the following:

public delegate int WhichIsFirst(object obj1, object obj2);

This declaration defines a delegate named WhichIsFirst which will encapsulate any method that takes two objects as parameters and that returns an int.

Once the delegate is defined, you can encapsulate a member method with that delegate by instantiating the delegate, passing in a method that matches the return type and signature.

Using Delegates to Specify Methods at Runtime

Delegates are used to specify the kinds of methods that can be used to handle events and to implement callbacks in your applications. They can also be used to specify static and instance methods that won’t be known until runtime.

Suppose, for example, that you want to create a simple container class called a Pair that can hold and sort any two objects passed to it. You can’t know in advance what kind of objects a Pair will hold, but by creating methods within those objects to which the sorting task can be delegated, you can delegate responsibility for determining their order to the objects themselves.

Different objects will sort differently; for example, a Pair of counter objects might sort in numeric order, while a Pair of Buttons might sort alphabetically by their name. As the author of the Pair class, you want the objects in the pair to have the responsibility of knowing which should be first and which should be second. To accomplish this, you will insist that the objects to be stored in the Pair must provide a method that tells you how to sort the objects.

You define the method you require by creating a delegate that defines the signature and return type of the method the object (e.g., Button) must provide to allow the Pair to determine which object should be first and which should be second.

The Pair class defines a delegate, WhichIsFirst. The Sort method will take a parameter, an instance of WhichIsFirst. When the Pair needs to know how to order its objects it will invoke the delegate passing in its two objects as parameters. The responsibility for deciding which of the two objects comes first is delegated to the method encapsulated by the delegate.

To test the delegate, you will create two classes, a Dog class and a Student class. Dogs and Students have little in common, except that they both implement methods that can be encapsulated by WhichComesFirst, and thus both Dog objects and Student objects are eligible to be held within Pair objects.

In the test program you will create a couple of Students and a couple of Dogs, and store them each in a Pair. You will then create delegate objects to encapsulate their respective methods that match the delegate signature and return type, and you’ll ask the Pair objects to sort the Dog and Student objects. Let’s take this step by step.

You begin by creating a Pair constructor that takes two objects and stashes them away in a private array:

public class Pair
{

    // two objects, added in order received
    public Pair(object firstObject, object secondObject)
    {
        thePair[0] = firstObject;
        thePair [1] = secondObject;
    }
    // hold both objects
    private object[]thePair = new object[2];

Next, you override ToString( ) to obtain the string value of the two objects:

public override string ToString(  )
{
    return thePair [0].ToString() + ", " + thePair [1].ToString(  );
}

You now have two objects in your Pair and you can print out their values. You’re ready to sort them and print the results of the sort. You can’t know in advance what kind of objects you will have, so you would like to delegate the responsibility of deciding which object comes first in the sorted Pair to the objects themselves. Thus, you require that each object stored in a Pair implement a method to return which of the two comes first. The method will take two objects (of whatever type) and return an enumerated value: theFirstComesFirst if the first object comes first, and theSecondComesFirst if the second does.

These required methods will be encapsulated by the delegate WhichIsFirst that you define within the Pair class:

public delegate comparison
WhichIsFirst(object obj1, object obj2);

The return value is of type comparison, the enumeration.

public enum comparison
{
   theFirstComesFirst = 1,
   theSecondComesFirst = 2
}

Any static method that takes two objects and returns a comparison can be encapsulated by this delegate at runtime.

You can now define the Sort method for the Pair class:

public void Sort(WhichIsFirst theDelegatedFunc)
{
   if (theDelegatedFunc(thePair[0],thePair[1]) == 
      comparison.theSecondComesFirst)
   {
      object temp = thePair[0];
      thePair[0] = thePair[1];
      thePair[1] = temp;
   }
}

This method takes a parameter: a delegate of type WhichIsFirst named theDelegatedFunc. The Sort( ) method delegates responsibility for deciding which of the two objects in the Pair comes first to the method encapsulated by that delegate. In the body of the Sort( ) method it invokes the delegated method and examines the return value, which will be one of the two enumerated values of comparsion.

If the value returned is theSecondComesFirst, the objects within the pair are swapped; otherwise no action is taken.

Notice that theDelegatedFunc is the name of the parameter to represent the method encapsulated by the delegate. You can assign any method (with the appropriate return value and signature) to this parameter. It is as if you had a method which took an int as a parameter:

int SomeMethod (int myParam){//...}

The parameter name is myParam, but you can pass in any int value or variable. Similarly the parameter name in the delegate example is theDelegatedFunc, but you can pass in any method that meets the return value and signature defined by the delegate WhichIsFirst.

Imagine you are sorting students by name. You write a method that returns theFirstComesFirst if the first student’s name comes first and theSecondComesFirst if the second student’s name does. If you pass in “Amy, Beth” the method will return theFirstComesFirst, and if you pass in “Beth, Amy” it will return theSecondComesFirst. If you get back theSecondComesFirst, the Sort method reverses the items in its array, setting Amy to the first position and Beth to the second.

Now add one more method, ReverseSort, which will put the items into the array in reverse order:

public void ReverseSort(WhichIsFirst theDelegatedFunc)
{
    if (theDelegatedFunc(thePair[0], thePair[1]) ==
		comparison.theFirstComesFirst)
    {
        object temp = thePair[0];
        thePair[0] = thePair[1];
        thePair[1] = temp;
    }
}

The logic here is identical to the Sort( ), except that this method performs the swap if the delegated method says that the first item comes first. Because the delegated function thinks the first item comes first, and this is a reverse sort, the result you want is for the second item to come first. This time if you pass in “Amy, Beth,” the delegated function returns theFirstComesFirst (i.e., Amy should come first), but because this is a reverse sort it swaps the values, setting Beth first. This allows you to use the same delegated function as you used with Sort, without forcing the object to support a function that returns the reverse sorted value.

Now all you need are some objects to sort. You’ll create two absurdly simple classes: Student and Dog. Assign Student objects a name at creation:

public class Student
{
    public Student(string name)
    {
        this.name = name;
    }

The Student class requires two methods, one to override ToString( ) and the other to be encapsulated as the delegated method.

Student must override ToString( ) so that the ToString( ) method in Pair, which invokes ToString( ) on the contained objects, will work properly: the implementation does nothing more than return the student’s name (which is already a string object):

public override string ToString(  )
{
     return name;
}

It must also implement a method to which Pair.Sort( ) can delegate the responsibility of determining which of two objects comes first:

return (String.Compare(s1.name, s2.name) < 0 ? 
   comparison.theFirstComesFirst : 
   comparison.theSecondComesFirst);

String.Compare is a .NET Framework method on the String class which compares two strings and returns less than zero if the first is smaller and greater than zero if the second is smaller, and returns zero if they are the same. This method is discussed in some detail in Chapter 10. Notice that the logic here returns theFirstComesFirst only if the first string is smaller; if they are the same or the second is larger, this method returns theSecondComesFirst.

Notice that the WhichStudentComesFirst( ) method takes two objects as parameters and returns a comparison. This qualifies it to be a Pair.WhichIsFirst delegated method, whose signature and return value it matches.

The second class is Dog. For our purposes, Dog objects will be sorted by weight, lighter dogs before heavier. Here’s the complete declaration of Dog:

public class Dog
{
    public Dog(int weight)
    {
        this.weight=weight;
    }

      // dogs are ordered by weight
      public static comparison WhichDogComesFirst(
         Object o1, Object o2)
      {
         Dog d1 = (Dog) o1;
         Dog d2 = (Dog) o2;
         return d1.weight > d2.weight ? 
         theSecondComesFirst : 
            theFirstComesFirst;
      }
    public override string ToString(  )
    {
        return weight.ToString(  );
    }
    private int weight;
}

Notice that the Dog class also overrides ToString and implements a static method with the correct signature for the delegate. Notice also that the Dog and Student delegate methods do not have the same name. They do not need to have the same name, as they will be assigned to the delegate dynamically at runtime.

Tip

You can call your delegated method names anything you like, but creating parallel names (e.g., WhichDogComesFirst and WhichStudentComesFirst) makes the code easier to read, understand, and maintain.

Example 12-1 is the complete program, which illustrates how the delegate methods are invoked.

Example 12-1. Working with delegates

namespace Programming_CSharp
{
   using System;

   public enum comparison
   {
      theFirstComesFirst = 1,
      theSecondComesFirst = 2
   }

   // a simple collection to hold 2 items
   public class Pair
   {
      // the delegate declaration
      public delegate comparison 
         WhichIsFirst(object obj1, object obj2);

      // passed in constructor take two objects, 
      // added in order received
      public Pair(
         object firstObject, 
         object secondObject)
      {
         thePair[0] = firstObject;
         thePair[1] = secondObject;
      }

      // public method which orders the two objects
      // by whatever criteria the object likes!
      public void Sort(
         WhichIsFirst theDelegatedFunc)
      {
         if (theDelegatedFunc(thePair[0],thePair[1]) 
            == comparison.theSecondComesFirst)
         {
            object temp = thePair[0];
            thePair[0] = thePair[1];
            thePair[1] = temp;
         }
      }

      // public method which orders the two objects
      // by the reverse of whatever criteria the object likes!
      public void ReverseSort(
         WhichIsFirst theDelegatedFunc)
      {
         if (theDelegatedFunc(thePair[0],thePair[1]) == 
            comparison.theFirstComesFirst)
         {
            object temp = thePair[0];
            thePair[0] = thePair[1];
            thePair[1] = temp;
         }
      }

      // ask the two objects to give their string value
      public override string ToString(  )
      {
         return thePair[0].ToString(  ) + ", " 
            + thePair[1].ToString(  );
      }


      // private array to hold the two objects
      private object[] thePair = new object[2];  
   }

   public class Dog
   {
      public Dog(int weight)
      {
         this.weight=weight;
      }

      // dogs are ordered by weight
      public static comparison WhichDogComesFirst(
         Object o1, Object o2)
      {
         Dog d1 = (Dog) o1;
         Dog d2 = (Dog) o2;
         return d1.weight > d2.weight ? 
            comparison.theSecondComesFirst : 
            comparison.theFirstComesFirst;
      }
      public override string ToString(  )
      {
         return weight.ToString(  );
      }
      private int weight;
   }

   public class Student
   {
      public Student(string name)
      {
         this.name = name;
      }

      // students are ordered alphabetically
      public static comparison 
         WhichStudentComesFirst(Object o1, Object o2)
      {
         Student s1 = (Student) o1;
         Student s2 = (Student) o2;
         return (String.Compare(s1.name, s2.name) < 0 ? 
            comparison.theFirstComesFirst : 
            comparison.theSecondComesFirst);
      }

      public override string ToString(  )
      {
         return name;
      }
      private string name;
   }

   public class Test
   {
      public static void Main(  )
      {
         // create two students and two dogs
         // and add them to Pair objects
         Student Jesse = new Student("Jesse");
         Student Stacey = new Student ("Stacey");
         Dog Milo = new Dog(65);
         Dog Fred = new Dog(12);

         Pair studentPair = new Pair(Jesse,Stacey);
         Pair dogPair = new Pair(Milo, Fred);
         Console.WriteLine("studentPair			: {0}", 
            studentPair.ToString(  ));
         Console.WriteLine("dogPair				: {0}", 
            dogPair.ToString(  ));

         // Instantiate  the delegates
         Pair.WhichIsFirst  theStudentDelegate = 
            new Pair.WhichIsFirst(
            Student.WhichStudentComesFirst);

         Pair.WhichIsFirst theDogDelegate =
            new Pair.WhichIsFirst(
            Dog.WhichDogComesFirst);

         // sort using the delegates
         studentPair.Sort(theStudentDelegate);
         Console.WriteLine("After Sort studentPair		: {0}", 
            studentPair.ToString(  ));
         studentPair.ReverseSort(theStudentDelegate);
         Console.WriteLine("After ReverseSort studentPair	: {0}", 
            studentPair.ToString(  ));
            
         dogPair.Sort(theDogDelegate);
         Console.WriteLine("After Sort dogPair		: {0}", 
            dogPair.ToString(  ));
         dogPair.ReverseSort(theDogDelegate);
         Console.WriteLine("After ReverseSort dogPair	: {0}", 
            dogPair.ToString(  ));
      }
   }
}

Output:
studentPair                     : Jesse, Stacey
dogPair                         : 65, 12
After Sort studentPair          : Jesse, Stacey
After ReverseSort studentPair   : Stacey, Jesse
After Sort dogPair              : 12, 65
After ReverseSort dogPair       : 65, 12

The Test program creates two Student objects and two Dog objects and then adds them to Pair containers. The student constructor takes a string for the student’s name and the dog constructor takes an int for the dog’s weight.

Student Jesse = new Student("Jesse");
Student Stacey = new Student ("Stacey");
Dog Milo = new Dog(65);
Dog Fred = new Dog(12);

Pair studentPair = new Pair(Jesse,Stacey);
Pair dogPair = new Pair(Milo, Fred);
Console.WriteLine("studentPair			: {0}", 
   studentPair.ToString(  ));
Console.WriteLine("dogPair				: {0}", 
   dogPair.ToString(  ));

It then prints the contents of the two Pair containers to see the order of the objects. The output looks like this:

studentPair           : Jesse, Stacey
dogPair               : 65, 12

As expected, the objects are in the order in which they were added to the Pair containers. We next instantiate two delegate objects:

Pair.WhichIsFirst  theStudentDelegate = 
   new Pair.WhichIsFirst(
   Student.WhichStudentComesFirst);

Pair.WhichIsFirst theDogDelegate =
   new Pair.WhichIsFirst(
   Dog.WhichDogComesFirst);

The first delegate, theStudentDelegate, is created by passing in the appropriate static method from the Student class. The second delegate, theDogDelegate, is passed a static method from the Dog class.

The delegates are now objects that can be passed to methods. You pass the delegates first to the Sort method of the Pair object, and then to the ReverseSort method. The results are printed to the console:

After Sort studentPair          : Jesse, Stacey
After ReverseSort studentPair   : Stacey, Jesse
After Sort dogPair              : 12, 65
After ReverseSort dogPair       : 65, 12

Static Delegates

A disadvantage of Example 12-1 is that it forces the calling class, in this case Test, to instantiate the delegates it needs in order to sort the objects in a Pair. It would be nice to get the delegate from the Student or Dog class. You can do this by giving each class its own static delegate. Thus, you can modify Student to add this:

public static readonly Pair.WhichIsFirst OrderStudents =
   new Pair.WhichIsFirst(Student.WhichStudentComesFirst);

This creates a static, readonly delegate named OrderStudents.

Tip

Marking OrderStudents readonly denotes that once this static field is created, it will not be modified.

You can create a similar delegate within Dog:

public static readonly Pair.WhichIsFirst OrderDogs =
    new Pair.WhichIsFirst(Dog. WhichDogComesFirst);

These are now static fields of their respective classes. Each is prewired to the appropriate method within the class. You can invoke delegates without declaring a local delegate instance. You just pass in the static delegate of the class:

studentPair.Sort(theStudentDelegate);
Console.WriteLine("After Sort studentPair		: {0}", 
    studentPair.ToString(  ));
 studentPair.ReverseSort(Student.OrderStudents);
 Console.WriteLine("After ReverseSort studentPair	: {0}", 
    studentPair.ToString(  ));
    
 dogPair.Sort(Dog.OrderDogs);
 Console.WriteLine("After Sort dogPair		: {0}", 
    dogPair.ToString(  ));
 dogPair.ReverseSort(Dog.OrderDogs);
 Console.WriteLine("After ReverseSort dogPair	: {0}", 
    dogPair.ToString(  ));

The output from these changes is identical to the previous example.

Delegates as Properties

The problem with static delegates is that they must be instantiated, whether or not they are ever used, as with Student and Dog in the previous example. You can improve these classes by changing the static delegate fields to properties.

For Student, you take out the declaration:

public static readonly Pair.WhichIsFirst OrderStudents =
   new Pair.WhichIsFirst(Student.WhichStudentComesFirst);

and replace it with:

public static Pair.WhichIsFirst OrderStudents
{
    get
    {
        return new Pair.WhichIsFirst(WhichStudentComesFirst);
    }
}

Similarly, you replace the Dog static field with:

public static Pair.WhichIsFirst OrderDogs
{
    get
    {
        return new Pair.WhichIsFirst(WhichDogComesFirst);
    }
}

Note

The assignment of the delegates is unchanged:

studentPair.Sort(Student.OrderStudents);
dogPair.Sort(Dog.OrderDogs);

When the OrderStudent property is accessed, the delegate is created:

return new Pair.WhichIsFirst(WhichStudentComesFirst);

The key advantage is that the delegate is not created until it is requested. This allows the test class to determine when it needs a delegate but still allows the details of the creation of the delegate to be the responsibility of the Student (or Dog) class.

Setting Order of Execution with Arrays of Delegates

Delegates can help you create a system in which the user can dynamically decide on the order of operations. Suppose you have an image processing system in which an image can be manipulated in a number of well-defined ways, such as blurring, sharpening, rotating, filtering, and so forth. Assume, as well, that the order in which these effects are applied to the image is important. The user wishes to choose from a menu of effects, applying all that he likes, and then telling the image processor to run the effects, one after the other in the order that he has specified.

You can create delegates for each operation and add them to an ordered collection, such as an array, in the order you’d like them to execute. Once all the delegates are created and added to the collection, you simply iterate over the array, invoking each delegated method in turn.

You begin by creating a class Image to represent the image that will be processed by the ImageProcessor:

public class Image
{
    public Image(  )
    {
        Console.WriteLine("An image created");
    }
}

You can imagine that this stands in for a .gif or .jpeg file or other image.

The ImageProcessor then declares a delegate. You can of course define your delegate to return any type and take any parameters you like. For this example you’ll define the delegate to encapsulate any method that returns void and takes no arguments:

public delegate void DoEffect(  );

The ImageProcessor then declares a number of methods, each of which processes an image and each of which matches the return type and signature of the delegate:

public static void Blur(  )
 {
     Console.WriteLine("Blurring image");
 }

 public static void Filter(  )
 {
     Console.WriteLine("Filtering image");
 }

 public static void Sharpen(  )
 {
     Console.WriteLine("Sharpening image");
 }

 public static void Rotate(  )
 {
     Console.WriteLine("Rotating image");
 }

Tip

In a production environment these methods would be very complicated, and they’d actually do the work of blurring, filtering, sharpening, and rotating the image.

The ImageProcessor class needs an array to hold the delegates that the user picks, a variable to hold the running total of how many effects are in the array, and of course a variable for the image itself:

DoEffect[] arrayOfEffects;
Image image;
int numEffectsRegistered = 0;

The ImageProcessor also needs a method to add delegates to the array:

public void AddToEffects(DoEffect theEffect)
{
    if (numEffectsRegistered >= 10)
    {
        throw new Exception("Too many members in array");
    }
    arrayOfEffects[numEffectsRegistered++] = theEffect;
    
}

It needs another method to actually call each method in turn:

public void ProcessImages(  )
{
    for (int i = 0;i < numEffectsRegistered;i++)
    {
        arrayOfEffects[i](  );
    }
}

Finally, you need only declare the static delegates that the client can call, hooking them to the processing methods:

public DoEffect BlurEffect = new DoEffect(Blur);
public DoEffect SharpenEffect = new DoEffect(Sharpen);
public DoEffect FilterEffect = new DoEffect(Filter);
public DoEffect RotateEffect = new DoEffect(Rotate);

Tip

In a production environment in which you might have dozens of effects, you might choose to make these properties rather than static methods. That would save creating the effects unless they are needed at the cost of making the program slightly more complicated.

The client code would normally have an interactive user-interface component, but we’ll simulate that by choosing the effects, adding them to the array, and then calling ProcessImage, as shown in Example 12-2.

Example 12-2. Using an array of delegates

namespace Programming_CSharp
{
   using System;

   // the image which we'll manipulate
   public class Image
   {
      public Image(  )
      {
         Console.WriteLine("An image created");
      }
   }

   public class ImageProcessor
   {
      // declare the delegate
      public delegate void DoEffect(  );

      // create various static delegates tied to member methods
      public DoEffect BlurEffect = 
         new DoEffect(Blur);
      public DoEffect SharpenEffect = 
         new DoEffect(Sharpen);
      public DoEffect FilterEffect = 
         new DoEffect(Filter);
      public DoEffect RotateEffect = 
         new DoEffect(Rotate);

      // the constructor initializes the image and the array
      public ImageProcessor(Image image)
      {
         this.image = image;
         arrayOfEffects = new DoEffect[10];
      }

      // in a production environment we'd use a more
      // flexible collection.
      public void AddToEffects(DoEffect theEffect)
      {
         if (numEffectsRegistered >= 10)
         {
            throw new Exception(
               "Too many members in array");
         }
         arrayOfEffects[numEffectsRegistered++] 
            = theEffect;
            
      }

      // the image processing methods...
      public static void Blur(  )
      {
         Console.WriteLine("Blurring image");
      }

      public static void Filter(  )
      {
         Console.WriteLine("Filtering image");
      }

      public static void Sharpen(  )
      {
         Console.WriteLine("Sharpening image");
      }

      public static void Rotate(  )
      {
         Console.WriteLine("Rotating image");
      }



      public void ProcessImages(  )
      {
         for (int i = 0;i < numEffectsRegistered;i++)
         {
            arrayOfEffects[i](  );
         }
      }

      // private member variables...
      private DoEffect[] arrayOfEffects;
      private Image image;
      private int numEffectsRegistered = 0;

   }

   // test driver
   public class Test
   {
      public static void Main(  )
      {
         Image theImage = new Image(  );

         // no ui to keep things simple, just pick the
         // methods to invoke, add them in the required
         // order, and then call on the image processor to 
         // run them in the order added.
         ImageProcessor theProc = 
            new ImageProcessor(theImage);
         theProc.AddToEffects(theProc.BlurEffect);
         theProc.AddToEffects(theProc.FilterEffect);
         theProc.AddToEffects(theProc.RotateEffect);
         theProc.AddToEffects(theProc.SharpenEffect);
         theProc.ProcessImages(  );
      }
   }
}

Output:
An image created
Blurring image
Filtering image
Rotating image
Sharpening image

In the Test class of Example 12-2, the ImageProcessor is instantiated and effects are added. If the user chooses to blur the image before filtering the image, it is a simple matter to add the delegates to the array in the appropriate order. Similarly, any given operation can be repeated as often as the user desires, just by adding more delegates to the collection.

You can imagine displaying the order of operations in a list box that might allow the user to reorder the methods, moving them up and down the list at will. As the operations are reordered you need only change their sort order in the collection. You might even decide to capture the order of operations to a database and then load them dynamically, instantiating delegates as dictated by the records you’ve stored in the database.

Delegates provide the flexibility to determine dynamically which methods will be called, in what order, and how often.

Multicasting

At times it is desirable to multicast : to call two implementing methods through a single delegate. This becomes particularly important when handling events (discussed later in this chapter).

The goal is to have a single delegate that invokes more than one method. This is different from having a collection of delegates, each of which invokes a single method. In the previous example, the collection was used to order the various delegates. It was possible to add a single delegate to the collection more than once and to use the collection to reorder the delegates to control their order of invocation.

With multicasting you create a single delegate that will call multiple encapsulated methods. For example, when a button is pressed you might want to take more than one action. You could implement this by giving the button a collection of delegates, but it is cleaner and easier to create a single multicast delegate.

Any delegate that returns void is a multicast delegate, though you can treat it as a single-cast delegate if you wish. Two multicast delegates can be combined with the addition operator (+). The result is a new multicast delegate that invokes both of the original implementing methods. For example, assuming Writer and Logger are delegates that return void, the following line will combine them and produce a new multicast delegate named myMulticastDelegate:

myMulticastDelegate = Writer + Logger;

You can add delegates to a multicast delegate using the plus-equals (+=) operator. This operator adds the delegate on the right side of the operator to the multicast delegate on the left. For example, assuming Transmitter and myMulticastDelegate are delegates, the following line adds Transmitter to myMulticastDelegate:

myMulticastDelegate += Transmitter;

To see how multicast delegates are created and used, let’s walk through a complete example. In Example 12-3, you create a class called MyClassWithDelegate which defines a delegate that takes a string as a parameter and returns void:

public delegate void StringDelegate(string s);

You then define a class called MyImplementingClass which has three methods, all of which return void and take a string as a parameter: WriteString, LogString, and TransmitString. The first writes the string to standard output, the second simulates writing to a log file, and the third simulates transmitting the string across the Internet. You instantiate the delegates to invoke the appropriate methods:

Writer("String passed to Writer
");
Logger("String passed to Logger
");
Transmitter("String passed to Transmitter
");

To see how to combine delegates, you create another Delegate instance:

MyClassWithDelegate.StringDelegate myMulticastDelegate;

and assign to it the result of “adding” two existing delegates:

myMulticastDelegate = Writer + Logger;

You add to this delegate an additional delegate using the += operator:

myMulticastDelegate += Transmitter;

Finally, you selectively remove delegates using the -= operator:

DelegateCollector -= Logger;

Example 12-3. Combining delegates

namespace Programming_CSharp
{
   using System;

   public class MyClassWithDelegate
   {
      // the delegate declaration
      public delegate void StringDelegate(string s);

   }

   public class MyImplementingClass
   {
      public static void WriteString(string s)
      {
         Console.WriteLine("Writing string {0}", s);
      }

      public static void LogString(string s)
      {
         Console.WriteLine("Logging string {0}", s);
      }

      public static void TransmitString(string s)
      {
         Console.WriteLine("Transmitting string {0}", s);
      }

   }


   public class Test
   {
      public static void Main(  )
      {
         // define three StringDelegate objects
         MyClassWithDelegate.StringDelegate 
            Writer, Logger, Transmitter;

         // define another StringDelegate
         // to act as the multicast delegate
         MyClassWithDelegate.StringDelegate 
            myMulticastDelegate;

         // Instantiate the first three delegates, 
         // passing in methods to encapsulate
         Writer = new MyClassWithDelegate.StringDelegate(
            MyImplementingClass.WriteString);
         Logger = new MyClassWithDelegate.StringDelegate(
            MyImplementingClass.LogString);
         Transmitter = 
            new MyClassWithDelegate.StringDelegate(
            MyImplementingClass.TransmitString);

         // Invoke the Writer delegate method
         Writer("String passed to Writer
");

         // Invoke the Logger delegate method 
         Logger("String passed to Logger
");

         // Invoke the Transmitter delegate method
         Transmitter("String passed to Transmitter
");
            
         // Tell the user you are about to combine
         // two delegates into the multicast delegate
         Console.WriteLine(
            "myMulticastDelegate = Writer + Logger");

         // combine the two delegates, the result is
         // assigned to myMulticast Delegate
         myMulticastDelegate = Writer + Logger;

         // Call the delegated methods, two methods
         // will be invoked
         myMulticastDelegate(
            "First string passed to Collector");

         // Tell the user you are about to add
         // a third delegate to the multicast
         Console.WriteLine(
            "
myMulticastDelegate += Transmitter");

         // add the third delegate
         myMulticastDelegate += Transmitter;

         // invoke the three delegated methods
         myMulticastDelegate(
            "Second string passed to Collector");

         // tell the user you are about to remove
         // the logger delegate
         Console.WriteLine(
            "
myMulticastDelegate -= Logger");

         // remove the logger delegate
         myMulticastDelegate -= Logger;

         // invoke the two remaining 
         // delegated methods
         myMulticastDelegate(
            "Third string passed to Collector");
      }
   }
}

 Output:
Writing string String passed to Writer

Logging string String passed to Logger

Transmitting string String passed to Transmitter

myMulticastDelegate = Writer + Logger
Writing string First string passed to Collector
Logging string First string passed to Collector

myMulticastDelegate += Transmitter
Writing string Second string passed to Collector
Logging string Second string passed to Collector
Transmitting string Second string passed to Collector

myMulticastDelegate -= Logger
Writing string Third string passed to Collector
Transmitting string Third string passed to Collector

In the test portion of Example 12-3, the delegate instances are defined and the first three (Writer, Logger, and Transmitter) are invoked. The fourth delegate, myMulticastDelegate, is then assigned the combination of the first two and it is invoked, causing both delegated methods to be called. The third delegate is added, and when myMulticastDelegate is invoked all three delegated methods are called. Finally, Logger is removed, and when myMulticastDelegate is invoked only the two remaining methods are called.

The power of multicast delegates is best understood in terms of events, discussed in the next section. When an event such as a button press occurs, an associated multicast delegate can invoke a series of event handler methods which will respond to the event.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.254.151