Chapter 17. 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 responsibility 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 it. 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, and so forth.

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# via 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.”

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.

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

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

At times, it is desirable to call two (or more) 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. For example, when a button is pressed, you might want to take more than one action. This process of calling more than one method with a single delegate is called multicasting .

Two 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, 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;

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 that will respond to the event.

GUIs, such as Microsoft Windows and web browsers, require that programs respond to events. An event might be a button push, a menu selection, the completion of a file transfer, and so forth. In short, something happens and you must respond to it. You cannot predict the order in which events will arise. The system is quiescent until the event, and then springs into action to handle it.

In a GUI environment, any number of controls can raise an event. For example, when you click a button, it might raise the Click event. When you add to a drop-down list, it might raise a ListChanged event.

Other classes will be interested in responding to these events. How they respond is not of interest to the class raising the event. The button says, “I was clicked,” and the responding classes react appropriately.

In C#, any object can publish a set of events to which other classes can subscribe. When the publishing class raises an event, all the subscribed classes are notified. With this mechanism, your object can say “Here are things I can notify you about,” and other classes might sign up, saying “Yes, let me know when that happens.” For example, a button might notify any number of interested observers when it is clicked. The button is called the publisher because the button publishes the Click event and the other classes are the subscribers because they subscribe to the Click event.

As a second example, a Clock might notify interested classes whenever the time changes by one second. The Clock class could itself be responsible for the User Interface representation of the time, rather than raising an event, so why bother with the indirection of using delegates? The advantage of the publish/subscribe idiom is that the Clock class need not know how its information will be used; the monitoring of the time is thus decoupled from the representation of that information. In addition, any number of classes can be notified when an event is raised. The subscribing classes do not need to know how the Clock works, and the Clock does not need to know what they are going to do in response to the event.

The publisher and the subscribers are decoupled by the delegate. This is highly desirable; it makes for more flexible and robust code. The Clock can change how it detects time without breaking any of the subscribing classes. The subscribing classes can change how they respond to time changes without breaking the Clock. The two classes spin independently of one another, and that makes for code that is easier to maintain.

Events in C# are implemented with delegates . The publishing class defines a delegate. The subscribing class does two things: first, it creates a method that matches the signature of the delegate, and then it creates an instance of that delegate type encapsulating that method. When the event is raised, the subscribing class’s methods are invoked through the delegate.

A method that handles an event is called an event handler. You can declare your event handlers as you would any other delegate.

By convention, event handlers in the .NET Framework return void and take two parameters. The first parameter is the “source” of the event (that is, the publishing object). The second parameter is an object derived from EventArgs . It is recommended that your event handlers follow this design pattern.

EventArgs is the base class for all event data. Other than its constructor, the EventArgs class inherits all its methods from Object, though it does add a public static field named Empty, which represents an event with no state (to allow for the efficient use of events with no state). The EventArgs-derived class contains information about the event.

Suppose you want to create a Clock class that uses delegates to notify potential subscribers whenever the local time changes value by one second. Call this delegate SecondChangeEventHandler :

The declaration for the SecondChangeEventHandler delegate is:

    public delegate void SecondChangeEventHandler: (
        object clock,
        TimeInfoEventArgs timeInformation
        );

This delegate will encapsulate any method that returns void and that takes two parameters. The first parameter is an object that represents the clock (the object raising the event), and the second parameter is an object of type, TimeInfoEventArgs, that will contain useful information for anyone interested in this event. TimeInfoEventArgs is defined as follows:

    public class TimeInfoEventArgs : EventArgs
    {
         public TimeInfoEventArgs(int hour, int minute, int second)
         {
             this.hour = hour;
             this.minute = minute;
             this.second = second;
         }
         public readonly int Hour;
         public readonly int Minute;
         public readonly int Second;

    }

The TimeInfoEventArgs object will have information about the current hour, minute, and second. It defines a constructor and three public, read-only integer variables.

In addition to its delegate, a Clock has three member variables—hour, minute, and second—as well as a single method, Run( ):

    public void Run(  )
    {
        for(;;)
        {
            // sleep 10 milliseconds
            Thread.Sleep(10);

            // get the current time
            System.DateTime dt = System.DateTime.Now;

            // if the second has changed
            // notify the subscribers
            if (dt.Second != second)
            {
                // create the TimeInfoEventArgs object
                // to pass to the subscriber
        TimeInfoEventArgs timeInformation =
          new TimeInfoEventArgs(
          dt.Hour,dt.Minute,dt.Second);

                // if anyone has subscribed, notify them
                if (SecondChanged != null)
                {
                    SecondChanged(this,timeInformation);
                }
            }
            // update the state
            this.second = dt.Second;
            this.minute = dt.Minute;
            this.hour = dt.Hour;
        }
    }

Run( ) creates an infinite for loop that periodically checks the system time. If the time has changed from the Clock object’s current time, it notifies all its subscribers and then updates its own state.

The first step is to sleep for 10 milliseconds:

    Thread.Sleep(10);

This makes use of a static method of the Thread class from the System.Threading namespace. The call to Sleep( ) prevents the loop from running so tightly that little else on the computer gets done.

After sleeping for 10 milliseconds, the method checks the current time:

    System.DateTime dt = System.DateTime.Now;

About every 100 times it checks, the second will have incremented. The method notices that change and notifies its subscribers. To do so, it first creates a new TimeInfoEventArgs object:

    if (dt.Second != second)
    {
       // create the TimeInfoEventArgs object
       // to pass to the subscriber
       TimeInfoEventArgs timeInformation =
          new TimeInfoEventArgs(dt.Hour,dt.Minute,dt.Second);

It then notifies the subscribers by firing the SecondChanged event:

       // if anyone has subscribed, notify them
       if (SecondChanged != null)
       {
          SecondChanged(this,timeInformation);
       }
    }

If an event has no subscribers registered, it will evaluate to null. The preceding test checks that the value is not null, ensuring that there are subscribers before calling SecondChanged.

You will remember that SecondChanged takes two arguments: the source of the event and the object derived from EventArgs. In the snippet, you see that the clock’s this reference is passed because the clock is the source of the event. The second parameter is the TimeInfoEventArgs object, timeInformation, created in the preceding snippet.

Raising the event will invoke whatever methods have been registered with the Clock class through the delegate. We’ll examine this in a moment.

Once the event is raised, you update the state of the Clock class:

    this.second = dt.Second;
    this.minute = dt.Minute;
    this.hour = dt.Hour;

All that is left is to create classes that can subscribe to this event. You’ll create two. First will be the DisplayClock class. The job of DisplayClock is not to keep track of time, but rather to display the current time to the console.

The example simplifies this class down to two methods. The first is a helper method named Subscribe( ) that is used to subscribe to the clock’s SecondChanged delegate. The second method is the event handler TimeHasChanged( ):

    public class DisplayClock
    {
        public void Subscribe(Clock theClock)
        {
            theClock.SecondChanged +=
                new Clock.SecondChangeHandler(TimeHasChanged);
        }
        public void TimeHasChanged(
            object theClock, TimeInfoEventArgs ti)
        {
                Console.WriteLine("Current Time: {0}:{1}:{2}",
                    ti.hour.ToString(  ),
                    ti.minute.ToString(  ),
                    ti.second.ToString(  ));
       }
    }

When the first method, Subscribe( ), is invoked, it creates a new SecondChangeHandler delegate, passing in its event handler method, TimeHasChanged( ). It then registers that delegate with the SecondChanged event of Clock.

You will create a second class that will also respond to this event, LogCurrentTime. This class would normally log the event to a file, but for our demonstration purposes, it will log to the standard console:

    public class LogCurrentTime
    {
        public void Subscribe(Clock theClock)
        {
            theClock.SecondChanged +=
                new Clock.SecondChangeHandler(WriteLogEntry);
        }

        // this method should write to a file
        // we write to the console to see the effect
        // this object keeps no state
        public void WriteLogEntry(
            object theClock, TimeInfoEventArgs ti)
        {
            Console.WriteLine("Logging to file: {0}:{1}:{2}",
                ti.hour.ToString(  ),
                ti.minute.ToString(  ),
                ti.second.ToString(  ));
        }
    }

Although in this example, these two classes are very similar, in a production program, any number of disparate classes might subscribe to an event.

All that remains is to create a Clock class, create the DisplayClock class, and tell it to subscribe to the event. You then will create a LogCurrentTime class and tell it to subscribe as well. Finally, you’ll tell the Clock to run. All this is shown in Example 17-2 (you’ll need to press Ctrl-C to terminate this application).

Example 17-2. Implementing events with delegates
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading;
    namespace ImplementingEventsWithDelegates
    {
       // a class to hold the information about the event
       // in this case it will hold only information
       // available in the clock class, but could hold
       // additional state information
       public class TimeInfoEventArgs : EventArgs
       {
          public TimeInfoEventArgs( int hour, int minute, int second )
          {
             this.hour = hour;
             this.minute = minute;
             this.second = second;
          }
          public readonly int hour;
          public readonly int minute;
          public readonly int second;
       }

       // our subject -- it is this class that other classes
       // will observe. This class publishes one delegate:
       // SecondChanged.
       public class Clock
       {
          private int hour;
          private int minute;
          private int second;

          // the delegate the subscribers must implement
          public delegate void SecondChangeHandler
            (
               object clock,
               TimeInfoEventArgs timeInformation
            );

          // an instance of the delegate
          public SecondChangeHandler SecondChanged;

          // set the clock running
          // it will raise an event for each new second
          public void Run(  )
          {

             for ( ; ; )
             {
                // sleep 10 milliseconds
                Thread.Sleep( 10 );

                // get the current time
                System.DateTime dt = System.DateTime.Now;
                // if the second has changed
                // notify the subscribers
                if ( dt.Second != second )
                {
                   // create the TimeInfoEventArgs object
                   // to pass to the subscriber
                   TimeInfoEventArgs timeInformation =
                     new TimeInfoEventArgs(
                     dt.Hour, dt.Minute, dt.Second );

                   // if anyone has subscribed, notify them
                   if ( SecondChanged != null )
                   {
                      SecondChanged(
                        this, timeInformation );
                   }
                }

                // update the state
                this.second = dt.Second;
                this.minute = dt.Minute;
                this.hour = dt.Hour;

             }
          }
       }

       // an observer. DisplayClock subscribes to the
       // clock's events. The job of DisplayClock is
       // to display the current time
       public class DisplayClock
       {
          // given a clock, subscribe to
          // its SecondChangeHandler event
          public void Subscribe( Clock theClock )
          {
             theClock.SecondChanged +=
               new Clock.SecondChangeHandler( TimeHasChanged );
          }

          // the method that implements the
          // delegated functionality
          public void TimeHasChanged(
            object theClock, TimeInfoEventArgs ti )
          {
             Console.WriteLine( "Current Time: {0}:{1}:{2}",
               ti.hour.ToString(  ),
               ti.minute.ToString(  ),
               ti.second.ToString(  ) );
          }
       }
       // a second subscriber whose job is to write to a file
       public class LogCurrentTime
       {
          public void Subscribe( Clock theClock )
          {
             theClock.SecondChanged +=
               new Clock.SecondChangeHandler( WriteLogEntry );
          }

          // this method should write to a file
          // we write to the console to see the effect
          // this object keeps no state
          public void WriteLogEntry(
            object theClock, TimeInfoEventArgs ti )
          {
             Console.WriteLine( "Logging to file: {0}:{1}:{2}",
               ti.hour.ToString(  ),
               ti.minute.ToString(  ),
               ti.second.ToString(  ) );
          }
       }

       public class Test
       {
          public static void Main(  )
          {
             // create a new clock
             Clock theClock = new Clock(  );

             // create the display and tell it to
             // subscribe to the clock just created
             DisplayClock dc = new DisplayClock(  );
             dc.Subscribe( theClock );

             // create a Log object and tell it
             // to subscribe to the clock
             LogCurrentTime lct = new LogCurrentTime(  );
             lct.Subscribe( theClock );

             // Get the clock started
             theClock.Run(  );
          }
       }
    }

The output on my machine looks like this:

    Current Time: 14:53:56
    Logging to file: 14:53:56
    Current Time: 14:53:57
    Logging to file: 14:53:57
    Current Time: 14:53:58
    Logging to file: 14:53:58
    Current Time: 14:53:59
    Logging to file: 14:53:59
    Current Time: 14:54:0
    Logging to file: 14:54:0

The net effect of this code is to create two classes, DisplayClock and LogCurrentTime, both of which subscribe to a third class’ event (Clock.SecondChanged).

SecondChanged is a multicast delegate field, initially referring to nothing. In time, it refers to a single delegate, and then later to multiple delegates . When the observer classes wish to be notified, they create an instance of the delegate and then add these delegates to SecondChanged. For example, in DisplayClock’s Subscribe( ) method, you see this line of code:

    theClock.SecondChanged +=
      new Clock.SecondChangeHandler(TimeHasChanged);

It turns out that the LogCurrentTime class also wants to be notified. In its Subscribe( ) method is very similar code:

    public void Subscribe(Clock theClock)
    {
      theClock.SecondChanged +=
        new Clock.SecondChangeHandler(WriteLogEntry);
    }

There is a problem with Example 17-2, however. What if the LogCurrentTime class was not so considerate, and it used the assignment operator (=) rather than the subscribe operator (+=), as in the following:

    public void Subscribe(Clock theClock)
    {
      theClock.SecondChanged =
        new Clock.SecondChangeHandler(WriteLogEntry);
    }

If you make that one tiny change to the example, you’ll find that the Logger( ) method is called, but the DisplayClock method is not called. The assignment operator replaced the delegate held in the SecondChanged multicast delegate. This is not good.

A second problem is that other methods can call SecondChangeHandler directly. For example, you might add the following code to the Main( ) method of your Test class:

    Console.WriteLine("Calling the method directly!");
    System.DateTime dt = System.DateTime.Now.AddHours(2);

    TimeInfoEventArgs timeInformation =
      new TimeInfoEventArgs(
      dt.Hour,dt.Minute,dt.Second);

    theClock.SecondChanged(theClock, timeInformation);

Here, Main( ) has created its own TimeInfoEventArgs object and invoked SecondChanged directly. This runs fine, even though it is not what the designer of the Clock class intended. Here is the output:

    Calling the method directly!
    Current Time: 18:36:7
    Logging to file: 18:36:7
    Current Time: 16:36:7
    Logging to file: 16:36:7

The problem is that the designer of the Clock class intended the methods encapsulated by the delegate to be invoked only when the event is fired. Here, Main( ) has gone around through the back door and invoked those methods itself. What is more, it has passed in bogus data (passing in a time construct set to two hours into the future!).

How can you, as the designer of the Clock class, ensure that no one calls the delegated method directly? You can make the delegate private, but then it won’t be possible for clients to register with your delegate at all. What’s needed is a way to say, “This delegate is designed for event handling: you may subscribe and unsubscribe, but you may not invoke it directly.”

The solution to this dilemma is to use the event keyword. The event keyword indicates to the compiler that the delegate can only be invoked by the defining class, and that other classes can only subscribe to and unsubscribe from the delegate using the appropriate += and -= operators, respectively.

To fix your program, change your definition of SecondChanged from:

    public SecondChangeHandler SecondChanged;

to the following:

    publicevent SecondChangeHandler SecondChanged;

Adding the event keyword fixes both problems. Classes can no longer attempt to subscribe to the event using the assignment operator (=), as they could previously, nor can they invoke the event directly, as was done in Main( ) in the preceding example. Either of these attempts will now generate a compile error:

    The event 'Programming_CSharp.Clock.SecondChanged' can only appear on
    the left-hand side of += or -= (except when used from within the type
    'Programming_CSharp.Clock')

There are two ways of looking at SecondChanged now that you’ve modified it. In one sense, it is simply a delegate instance to which you’ve restricted access using the keyword event. In another, more important sense, SecondChanged is an event, implemented by a delegate of type SecondChangeHandler. These two statements mean the same thing, but the latter is a more object-oriented way of looking at it, and better reflects the intent of this keyword: to create an event that your object can raise, and to which other objects can respond.

The complete source, modified to use the event rather than the unrestricted delegate, is shown in Example 17-3.

Example 17-3. Using the event keyword
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace UsingTheEventKeyword
{
   // a class to hold the information about the event
   // in this case it will hold only information
   // available in the clock class, but could hold
   // additional state information
   public class TimeInfoEventArgs : EventArgs
   {
      public readonly int hour;
      public readonly int minute;
      public readonly int second;
      public TimeInfoEventArgs( int hour, int minute, int second )
      {
         this.hour = hour;
         this.minute = minute;
         this.second = second;
      }
   }

   // our subject -- it is this class that other classes
   // will observe. This class publishes one event:
   // SecondChanged. The observers subscribe to that event
   public class Clock
   {
      private int hour;
      private int minute;
      private int second;

      // the delegate the subscribers must implement
      public delegate void SecondChangeHandler
        (
        object clock,
        TimeInfoEventArgs timeInformation
        );

      // the keyword event controls access to the delegate
      public event SecondChangeHandler SecondChanged;

      // set the clock running
      // it will raise an event for each new second
      public void Run(  )
      {

         for ( ; ; )
         {
            // sleep 10 milliseconds
            Thread.Sleep( 10 );

            // get the current time
            System.DateTime dt = System.DateTime.Now;

            // if the second has changed
            // notify the subscribers
            if ( dt.Second != second )
            {
               // create the TimeInfoEventArgs object
               // to pass to the subscriber
               TimeInfoEventArgs timeInformation =
                 new TimeInfoEventArgs(
                 dt.Hour, dt.Minute, dt.Second );

               // if anyone has subscribed, notify them
               if ( SecondChanged != null )
               {
                  SecondChanged(
                    this, timeInformation );
               }
            }

            // update the state
            this.second = dt.Second;
            this.minute = dt.Minute;
            this.hour = dt.Hour;

         }
      }
   }

   // an observer. DisplayClock subscribes to the
   // clock's events. The job of DisplayClock is
   // to display the current time
   public class DisplayClock
   {
      // given a clock, subscribe to
      // its SecondChangeHandler event
      public void Subscribe( Clock theClock )
      {
         theClock.SecondChanged +=
           new Clock.SecondChangeHandler( TimeHasChanged );
      }
      // the method that implements the
      // delegated functionality
      public void TimeHasChanged(
        object theClock, TimeInfoEventArgs ti )
      {
         Console.WriteLine( "Current Time: {0}:{1}:{2}",
           ti.hour.ToString(  ),
           ti.minute.ToString(  ),
           ti.second.ToString(  ) );
      }
   }

   // a second subscriber whose job is to write to a file
   public class LogCurrentTime
   {
      public void Subscribe( Clock theClock )
      {
         theClock.SecondChanged +=
           new Clock.SecondChangeHandler( WriteLogEntry );
      }

      // this method should write to a file
      // we write to the console to see the effect
      // this object keeps no state
      public void WriteLogEntry(
        object theClock, TimeInfoEventArgs ti )
      {
         Console.WriteLine( "Logging to file: {0}:{1}:{2}",
           ti.hour.ToString(  ),
           ti.minute.ToString(  ),
           ti.second.ToString(  ) );
      }
   }

   public class Test
   {
      public static void Main(  )
      {
         // create a new clock
         Clock theClock = new Clock(  );

         // create the display and tell it to
         // subscribe to the clock just created
         DisplayClock dc = new DisplayClock(  );
         dc.Subscribe( theClock );

         // create a Log object and tell it
         // to subscribe to the clock
         LogCurrentTime lct = new LogCurrentTime(  );
         lct.Subscribe( theClock );

         // Get the clock started
         theClock.Run(  );
      }
   }
}

In the previous example, you subscribed to the event by invoking a new instance of the delegate, passing in the name of a method that implements the event:

    theClock.SecondChanged += TimeHasChanged;

Later in the code, you must define TimeHasChanged as a method that matches the signature of the SecondChangeHandler delegate:

    public void TimeHasChanged(
      object theClock, TimeInfoEventArgs ti)
    {
      Console.WriteLine("Current Time: {0}:{1}:{2}",
        ti.hour.ToString(  ),
        ti.minute.ToString(  ),
        ti.second.ToString(  ));
    }

Anonymous methods allow you to pass a code block rather than the name of the method. This can make for more efficient and easier to maintain code, and the anonymous method has access to the variables in the scope in which they are defined.

    clock.SecondChanged += delegate( object theClock,TimeInfoEventArgs ti )
    {
      Console.WriteLine( "Current Time: {0}:{1}:{2}",
      ti.hour.ToString(  ),
      ti.minute.ToString(  ),
      ti.second.ToString(  ) );
    };

Notice that rather than registering an instance of a delegate, you use the keyword delegate, followed by the parameters that would be passed to your method, followed by the body of your method encased in braces and terminated by a semicolon.

This “method” has no name; hence, it is anonymous. You cannot invoke the method except through the delegate; but that is exactly what you want.