Section 12.3. Events


12.3. Events

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 can't 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 widgets 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.

12.3.1. Publishing and Subscribing

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.

This design implements the Publish/Subscribe (Observer) Pattern described in the seminal work Design Patterns (Addison Wesley). 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."

Note that the publishing class doesn't know or care who (if anyone) subscribes; it just raises the event. Who responds to that event, and how they respond, isn't the concern of the publishing class.


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, instead of 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 don't need to know how the Clock works, and the Clock doesn't 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.

12.3.2. Events and Delegates

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.

VB6 programmers take note: C# doesn't put restrictions on the names of the methods that handle events. Also, the .NET implementation of the publish/subscribe model lets you have a single method that subscribes to multiple events.


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 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, read-only integer variables.

In addition to its delegate, a Clock has three member variableshour, minute, and secondas 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 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, which will be covered in some detail in Chapter 20. 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 OnSecondChange event:

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

If an event has no subscribers registered, it evaluates to null. The preceding test checks that the value isn't null, ensuring that there are subscribers before calling OnSecondChange.

Remember that OnSecondChange 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 on the line above.

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

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

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

No attempt has been made to make this code thread-safe. Thread safety and synchronization are discussed in Chapter 20.


All that is left is to create classes that can subscribe to this event. You create two. First is the DisplayClock class. The job of DisplayClock isn't 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 subscribes to the clock's OnSecondChange delegate. The second method is the event handler TimeHasChanged( ):

public class DisplayClock {     public void Subscribe(Clock theClock)     {         theClock.OnSecondChange +=             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 OnSecondChange event of Clock.

Now create a second class that also responds to this event, LogCurrentTime. This class normally logs the event to a file, but for our demonstration purposes, it logs to the standard console:

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( ));     } }

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 create a LogCurrentTime class and tell it to subscribe as well. Finally, tell the Clock to run. All this is shown in Example 12-3 (you need to press Ctrl-C to terminate this application).

Example 12-3. Implementing events with delegates
#region Using directives using System; using System.Collections.Generic; using System.Text; using System.Threading; #endregion namespace EventsWithDelegates {    // 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:     // OnSecondChange.    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 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;          }       }    }    // 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( );       }    } } Output: 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.OnSecondChange).

OnSecondChange 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 OnSecondChange. For example, in DisplayClock's Subscribe( ) method, you see this line of code:

theClock.OnSecondChange +=   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.OnSecondChange +=     new Clock.SecondChangeHandler(WriteLogEntry); }

12.3.3. Solving Delegate Problems with Events

There is a problem with Example 12-3, 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.OnSecondChange =     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 OnSecondChange multicast delegate. This isn't 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.OnSecondChange(theClock, timeInformation);

Here Main( ) has created its own TimeInfoEventArgs object and invoked OnSecondChange 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."

12.3.4. The event Keyword

The solution to this dilemma is to use the event keyword. The event keyword indicates to the compiler that the delegate can be invoked only 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 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.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 Example 12-4.

Example 12-4. Using the event keyword
#region Using directives using System; using System.Collections.Generic; using System.Text; using System.Threading; #endregion namespace EventKeyword {   // 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:    // OnSecondChange. 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 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;       }     }   }   // 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( );       }    } }



Programming C#(c) Building. NET Applications with C#
Programming C#: Building .NET Applications with C#
ISBN: 0596006993
EAN: 2147483647
Year: 2003
Pages: 180
Authors: Jesse Liberty

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