WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button
Crane

Using Delegates to Implement Event Handling

by Jesse Liberty, author of Programming C# 3.0, 5th Edition

06/23/2003

One of the key aspects of C# programming in particular, and .NET programming in general, is using delegates to handle events. In Programming C#, 3rd Edition, I approach teaching delegates and events somewhat differently than I had in previous editions.

This article will focus on one aspect of delegates: how they are used to implement event handling. It is important to understand that while delegates are a general-purpose mechanism for calling methods indirectly, their principal uses in .NET are for a) implementing events and b) implementing call-back methods.

To get a sense of how delegates are used to implement events, we'll look at the implementation of a custom event.

Implementing a Custom Event

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. This design is a form of the Observer Pattern described in the seminal work Design Patterns, by Gamma, et al. (Addison Wesley, 1995). Gamma describes the intent of this pattern: "Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically."

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 simply print 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 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.

Programming C#

Related Reading

Programming C#
By Jesse Liberty

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.

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 of 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 SecondChangeHandler.

The declaration for the SecondChangeHandler delegate is:

public delegate void SecondChangeHandler(
    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, readonly 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 (OnSecondChange != null)
            {
                OnSecondChange(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 of its subscribers and then updates its own state.

The first step is to sleep for 10 milliseconds:

Thread.Sleep(10);

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)
{
   TimeInfoEventArgs timeInformation = 
      new TimeInfoEventArgs(dt.Hour,dt.Minute,dt.Second);

It then notifies the subscribers by firing the OnSecondChange event (the if statement checks that the value is not null, ensuring that there are subscribers before calling OnSecondChange).

   if (OnSecondChange != null)
   {
      OnSecondChange(this,timeInformation);
   }

You will remember that OnSecondChange takes two arguments: the source of the event and the object derived from EventArgs. In the code 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 on the line above.

Pages: 1, 2, 3

Next Pagearrow