WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Using Delegates to Implement Event Handling
Pages: 1, 2, 3

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 is 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 OnSecondChange from

public SecondChangeHandler OnSecondChange; 

to the following:

public event SecondChangeHandler OnSecondChange;

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

The event 'Programming_CSharp.Clock.OnSecondChange' 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 OnSecondChange, 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, OnSecondChange 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 the following modified example:

Using the Keyword event

namespace Programming_CSharp
{
  using System;
  using System.Threading;

  // 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 event: 
  // OnSecondChange. The observers subscribe to that event
  public class Clock
  {
    // 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 OnSecondChange;

    // 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 (OnSecondChange != null)
          {
            OnSecondChange(
              this,timeInformation);
          }
        }

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

      }
    }
    private int hour;
    private int minute;
    private int second;
  }

  // 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.OnSecondChange +=
        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.OnSecondChange +=
        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();
    }
  }
}

You can see that the event keyword serves to modify how the delegate is used, to be consistent with the semantics of event handling. There is much more to the use of delegates, and this topic is covered in depth in Chapter 12 of Programming C#, 3rd Edition, from which this article is adapted.


O'Reilly & Associates recently (in May 2003) released Programming C#, 3rd Edition.

Jesse Liberty is a senior program manager for Microsoft Silverlight where he is responsible for the creation of tutorials, videos and other content to facilitate the learning and use of Silverlight. Jesse is well known in the industry in part because of his many bestselling books, including O'Reilly Media's Programming .NET 3.5, Programming C# 3.0, Learning ASP.NET with AJAX and the soon to be published Programming Silverlight.


Return to ONDotnet.com