Delegates & Events in C# – Part 2 : 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 Girl class and a Boy class. Girls and Boys have little in common, except that they both implement methods that can be encapsulated by WhichComesFirst, and thus both Girl objects and Boy objects are eligible to be held within Pair objects.

In the test program you will create a couple of Boys and a couple of Girls, 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 Girl and Boy 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]; 
 public override string ToString()
 {
  return thePair [0].ToString() + ", " + thePair [1].ToString();
 } 
}

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

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 Boys by name. You write a method that returns theFirstComesFirst if the first Boy’s name comes first and theSecondComesFirst if the second Boy’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: Boy and Girl. Assign Boy objects a name at creation:

 
public class Boy
{
 public Boy(string name) 
 {
  this.name = name;
 }
 public override string ToString() { return name; }
}

The Boy class requires two methods, one to override ToString( ) and the other to be encapsulated as the delegated method. Boy 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 Boy’s name (which is already a string object).

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. 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 WhichBoyComesFirst( ) 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 Girl. For our purposes, Girl objects will be sorted by weight, lighter Girls before heavier. Here’s the complete declaration of Girl:

public class Girl
{
 public Girl(int weight) { this.weight=weight; } 
 // Girls are ordered by weight
 public static comparison WhichGirlComesFirst( Object o1, Object o2) 
 { 
  Girl d1 = (Girl) o1;
  Girl d2 = (Girl) o2; 
  return d1.weight &gt; d2.weight ? theSecondComesFirst : <div><a href="http://buycialischeap-storein.com/">cialis online cost</a> <a href="http://viagraonline-canadarxed.com/">where to buy viagra online</a> <a href="http://viagranoprescription-buy.com/" rel="nofollow">viagra where buy</a> <a href="http://discountpharmacy-rxstore.com/" rel="nofollow">cvs pharmacy refill</a> <a href="http://cialisonline-genericrxed.com/">cialis online</a></div>  theFirstComesFirst;
 } 
 public override string ToString() 
 {
  return weight.ToString();
 } 
 private int weight; 
}

Notice that the Girl class also overrides ToString and implements a static method with the correct signature for the delegate. Notice also that the Girl and Boy 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.

Enjoyed this post? Share it!

 

Leave a comment

Your email address will not be published.