Section 17.1. Delegates


17.1. 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.

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. As an alternative, you can use anonymous methods as described below. In either case, the delegate can then be used to invoke that encapsulated method.

17.1.1. Using Delegates to Specify Methods at Runtime

Delegates decouple the class that declares the delegate from the class that uses the delegate. For example, suppose that you want to create a simple generic 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 can define this requirement with interfaces, as well. Delegates are smaller and of finer granularity than interfaces. The Pair class does not need to implement an entire interface; it just needs to define the signature and return type of the method it wants to invoke. That is what delegates are for: they define the return type and signature of methods that can be invoked through the interface.

In this case, the Pair class will declare a delegate named WhichIsFirst . When the Pair needs to know how to order its objects, it will invoke the delegate passing in its two member objects as parameters. The responsibility for deciding which of the two objects comes first is delegated to the method encapsulated by the delegate:

 public delegate  Comparison WhichIsFirst<T>  ( T obj1, T obj2 ); 

In this definition, WhichIsFirst is defined to encapsulate a method that takes two objects as parameters, and that returns an object of type Comparison . Comparison turns out to be an enumeration you will define:

 public enum Comparison     {        TheFirstComesFirst = 1,        TheSecondComesFirst = 2     } 

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 instances of WhichIsFirst to encapsulate their respective methods that will determine which Student or which Dog object should be first, and which second. 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 Pair( T firstObject, T secondObject )     {        thePair[0] = firstObject;        thePair[1] = secondObject;     } 

Note that the Pair<T> class uses generics, as discussed in Chapter 14. Therefore, firstObject and secondObject in the constructor above are of the generic type T, and the actual types will be assigned at runtime.

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 delegate the responsibility of deciding which object comes first in the sorted Pair to the objects themselves.

Both the Dog class and the Student class implement methods that can be encapsulated by WhichIsFirst . Any 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)        {           T temp = thePair[0];           thePair[0] = thePair[1];           thePair[1] = temp;        }     } 

This method takes a parameter: a delegate of type WhichIsFirst named theDelega-tedFunc . 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 Comparison .

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

This is analogous to how the other parameters work. If you had a method that 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 force the items in the array into the reverse of their normal order:

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

The logic here is identical to 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 (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, called WhichStudentComesFirst( ) in this case:

 public static Comparison WhichStudentComesFirst( Student s1, Student s2 )     {        return (String.Compare(s1.name, s2.name) < 0 ?           Comparison.theFirstComesFirst :           Comparison.theSecondComesFirst);     } 

String.Compare( ) is a .NET Framework method on the String class that compares two strings and returns less than zero if the first is smaller, greater than zero if the second is smaller, and zero if they are the same. This method is discussed in some detail in Chapter 15. 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 ?               Comparison.theSecondComesFirst :                 Comparison.theFirstComesFirst;         }         public override string ToString(  )         {             return weight.ToString(  );         }         private int weight;     } 

The Dog class also overrides ToString and implements a static method called Which-DogComesFirst( ) 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.

You can call your delegated method names anything you like, but creating parallel names (such as WhichDogComesFirst and WhichStudent-ComesFirst ) makes the code easier to read, understand, and maintain.


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

Example 17-1. Working with delegates
 using System; using System.Collections.Generic; using System.Text; namespace WorkingWithDelegates {    public enum Comparison    {       TheFirstComesFirst = 1,       TheSecondComesFirst = 2    }    // a simple collection to hold 2 items    public class Pair<T>    {       // private array to hold the two objects       private T[] thePair = new T[2];       // the delegate declaration       public delegate Comparison           WhichIsFirst( T obj1, T obj2 );       // passed in constructor take two objects,       // added in order received       public Pair(           T firstObject,           T 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 )          {             T 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 )          {             T 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(  );       }    }        // end class Pair    public class Dog    {       private int weight;       public Dog( int weight )       {          this.weight = weight;       }       // dogs are ordered by weight       public static Comparison WhichDogComesFirst(           Dog d1, Dog d2 )       {          return d1.weight > d2.weight ?              Comparison.TheFirstComesFirst :              Comparison.TheSecondComesFirst );       }       public override string ToString(  )       {          return weight.ToString(  );       }    }        // end class Dog    public class Student    {       private string name;       public Student( string name )       {          this.name = name;       }       // students are ordered alphabetically       public static Comparison           WhichStudentComesFirst( Student s1, Student s2 )       {          return ( String.Compare( s1.name, s2.name ) < 0 ?              Comparison.theFirstComesFirst :              Comparison.theSecondComesFirst );       }       public override string ToString(  )       {          return name;       }    }        // end class Student    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<Student> studentPair = new Pair<Student>( Jesse, Stacey );          Pair<Dog> dogPair = new Pair<Dog>( Milo, Fred );          Console.WriteLine( "studentPair\t\t\t: {0}",              studentPair.ToString(  ) );          Console.WriteLine( "dogPair\t\t\t\t: {0}",              dogPair.ToString(  ) );          // Instantiate  the delegates          Pair<Student>.WhichIsFirst theStudentDelegate =              new Pair<Student>.WhichIsFirst(              Student.WhichStudentComesFirst );          Pair<Dog>.WhichIsFirst theDogDelegate =              new Pair<Dog>.WhichIsFirst(              Dog.WhichDogComesFirst );          // sort using the delegates          studentPair.Sort( theStudentDelegate );          Console.WriteLine( "After Sort studentPair\t\t: {0}",              studentPair.ToString(  ) );          studentPair.ReverseSort( theStudentDelegate );          Console.WriteLine( "After ReverseSort studentPair\t: {0}",              studentPair.ToString(  ) );          dogPair.Sort( theDogDelegate );          Console.WriteLine( "After Sort dogPair\t\t: {0}",              dogPair.ToString(  ) );          dogPair.ReverseSort( theDogDelegate );          Console.WriteLine( "After ReverseSort dogPair\t: {0}",              dogPair.ToString(  ) );       }    } } 

The output looks like this:

 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<Student> studentPair = new Pair<Student>( Jesse, Stacey );     Pair<Dog> dogPair = new Pair<Dog>( Milo, Fred );     Console.WriteLine( "studentPair\t\t\t: {0}",         studentPair.ToString(  ) );     Console.WriteLine( "dogPair\t\t\t\t: {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. You next instantiate two delegate objects:

 Pair<Student>.WhichIsFirst theStudentDelegate =         new Pair<Student>.WhichIsFirst(         Student.WhichStudentComesFirst );     Pair<Dog>.WhichIsFirst theDogDelegate =         new Pair<Dog>.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 



Learning C# 2005
Learning C# 2005: Get Started with C# 2.0 and .NET Programming (2nd Edition)
ISBN: 0596102097
EAN: 2147483647
Year: 2004
Pages: 250

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