C H A P T E R  14

Events

Publishers and Subscribers

One common requirement in many programs is that when a particular program event occurs, other parts of the program need to be notified that the event has occurred.

One pattern for satisfying this requirement is called the publisher/subscriber pattern. In this pattern, a class, called the publisher, defines a set of events that other parts of the program might be interested in. Other classes can then “sign up” to be notified by the publisher when these events occur. These subscriber classes “sign up” for notification by supplying a method to the publisher. When the event occurs, the publisher “raises the event,” and all the methods submitted by the subscribers are executed.

The methods supplied by the subscribers are called callback methods, because the publisher “calls the subscribers back” by executing their methods. They are also called event handlers, because they are the code that is called to handle the event. Figure 14-1 illustrates the process, showing the publisher with an event and three subscribers to the event.

Image

Figure 14-1. Publishers and subscribers

The following are some important terms related to events:

  • Publisher: A class or struct that publishes an event so that other classes can be notified when the event occurs.
  • Subscriber: A class or struct that registers to be notified when the event occurs.
  • Event handler: A method that is registered with the publisher, by the subscriber, and is executed when the publisher raises the event. The event handler method can be declared in the same class or struct as the event or in a different class or struct.
  • Raising an event: The term for invoking or firing an event. When an event is raised, all the methods registered with that event are invoked.

The preceding chapter covered delegates. Many aspects of events are similar to those of delegates. In fact, an event is like a simpler delegate that is specialized for a particular use. There's good reason for the similarities in the behaviors of delegates and events. An event contains a private delegate, as illustrated in Figure 14-2.

The important things to know about an event's private delegate are the following:

  • An event gives structured access to its privately controlled delegate. That is, you can't directly access the delegate.
  • There are fewer operations available than with a delegate. With an event you can only add and remove event handlers, and invoke the event.
  • When an event is raised, it invokes the delegate, which sequentially calls the methods in its invocation list.

Notice in Figure 14-2 that only the += and -= operators are sticking out to the left of the event box. This is because they are the only operations (apart from invoking the event itself) allowed on an event.

Image

Figure 14-2. An event has an encapsulated delegate.

Figure 14-3 illustrates a program with a class called Incrementer, which performs a count of some sort.

  • Incrementer defines an event called CountedADozen, which it raises every time it counts another dozen items.
  • Subscriber classes Dozens and SomeOtherClass each have an event handler registered with the CountedADozen event.
  • Each time the event is raised, the handlers are called.
Image

Figure 14-3. Structure and terminology of a class with an event

Overview of Source Code Components

There are five pieces of code that need to be in place to use events. These are illustrated in Figure 14-4. I'll cover each of them in the following sections. These pieces of code are the following:

  • Delegate type declaration: The event and the event handlers must have a common signature and return type, which is described by a delegate type.
  • Event handler declarations: These are the declarations, in the subscriber classes, of the methods to be executed when the event is raised. These do not have to be explicitly named methods; they can also be anonymous methods or lambda expressions, as described in Chapter 13.
  • Event declaration: The publisher class must declare an event member that subscribers can register with. When a class declares a public event, it is said to have published the event.
  • Event registration: The subscribers must register with an event in order to be notified when it has been raised. This is the code that connects the event handlers to the event.
  • Code that raises the event: This is the code in the publisher that “fires” the event, causing it to invoke all the event handlers registered with it.
Image

Figure 14-4. The five source code components of using an event

Declaring an Event

The publisher must provide the event object. Creating an event is simple—it requires only a delegate type and a name. The syntax for an event declaration is shown in the following code, which declares an event called CountedADozen. Notice the following about event CountedADozen:

  • The event is declared inside a class.
  • It requires the name of a delegate type. Any event handlers attached to the event (i.e., registered with it) must match the delegate type in signature and return type.
  • It is declared public so that other classes and structs can register event handlers with it.
  • You do not use an object-creation expression (a new expression) with an event.
   class Incrementer
   {                      Keyword                            Name of event
                                          ↓                                           ↓      
      public event EventHandler CountedADozen;
                                                   ↑  
                                          Delegate type

You can declare more than one event in a declaration statement by using a comma-separated list. For example, the following statement declares three events:

   public event EventHandler MyEvent1, MyEvent2, OtherEvent;
                                                                                                ↑
                                                                                     Three events

You can also make events static by including the static keyword, as shown in the following declaration:

   public static event EventHandler CountedADozen;
                         ↑                           
                     Keyword

An Event Is a Member

A common error is to think of an event as a type—which it's not. Like a method, or a property, an event is a member of a class or a struct, and there are several important ramifications to this:

  • Because an event is a member
    • You cannot declare an event in a block of executable code.
    • It must be declared in a class or struct, with the other members.
  • An event member is implicitly and automatically initialized to null with the other members.

To declare an event, you must supply the name of a delegate type. You can either declare one or use one that already exists. If you declare a delegate type, it must specify the signature and return type of the methods that will be registered by the event.

The BCL declares a delegate called EventHandler specifically for use with system events. I'll describe the EventHandler delegate later in this chapter.

Subscribing to an Event

Subscribers add event handlers to the event. For an event handler to be added to an event, the handler must have the same return type and signature as the event's delegate.

  • Use the += operator to add an event handler to an event, as shown in the following code. The event handler is placed on the right-hand side of the operator.
  • The event handler specification can be any of the following:
    • The name of an instance method
    • The name of a static method
    • An anonymous method
    • A lambda expression

For example, the following code adds three methods to event CountedADozen. The first is an instance method. The second is a static method. The third is an instance method, using the delegate form.

       Class instance                                           Instance method
               ↓                                                                ↓                  
   incrementer.CountedADozen += IncrementDozensCount;        // Method reference form
   incrementer.CountedADozen += ClassB.CounterHandlerB;      // Method reference form
                                         ↑                                           ↑  
                              Event member                           Static method
   mc.CountedADozen += new EventHandler(cc.CounterHandlerC);  // Delegate form

Just as with delegates, you can use anonymous methods and lambda expressions to add event handlers. For example, the following code first uses a lambda expression and then uses an anonymous method.

   // Lambda expression
   incrementer.CountedADozen += () => DozensCount++;
   
   // Anonymous method
   incrementer.CountedADozen += delegate { DozensCount++; };

Raising an Event

The event member itself just holds the event handlers that need to be invoked. Nothing happens with them unless the event is raised. You need to make sure there is code to do just that, at the appropriate times.

For example, the following code raises event CountedADozen. Notice the following about the code:

  • Before raising the event, the code compares it to null to see whether it contains any event handlers. If the event is null, it is empty and cannot be executed.
  • The syntax for raising the event is the same as that of invoking a method:
    • Use the name of the event, followed by the parameter list enclosed in parentheses.
    • The parameter list must match the delegate type of the event.
   if (CountedADozen != null)              // Make sure there are methods to execute.
      CountedADozen (source, args);        // Raise the event.
                   ↑                           ↑  
    Event name                   Parameter list

Putting together the event declaration and the code to raise the event gives the following class declaration for the publisher. The code contains two members: the event and a method called DoCount, which raises the event when appropriate.

   class Incrementer
   {
      public event EventHandler CountedADozen;   // Declare the event.
   
      void DoCount(object source, EventArgs args)
      {
         for( int i=1; i < 100; i++ )
            if( i % 12 == 0 )
               if (CountedADozen != null)       // Make sure there are methods to execute.
                  CountedADozen(source, args);
      }                                           ↑
                                             Raise the event.
   }

The code in Figure 14-5 shows the whole program, with publisher class Incrementer and subscriber class Dozens. The things to note about the code are the following:

  • In its constructor, class Dozens subscribes to the event, supplying method IncrementDozensCount as its event handler.
  • In method DoCount in class Incrementer, event CountedADozen is raised each time the method increments another 12 times.
Image

Figure 14-5. A complete program with a publisher and a subscriber, showing the five segments of code necessary for using an event

The code in Figure 14-5 produces the following output:


Number of dozens = 8

Standard Event Usage

GUI programming is event driven, which means that while the program is running, it can be interrupted at any time by events such as button clicks, key presses, or system timers. When this happens, the program needs to handle the event and then continue on its course.

Clearly, this asynchronous handling of program events is the perfect situation for using C# events. Windows GUI programming uses events so extensively that there is a standard .NET Framework pattern for using them. The foundation of the standard pattern for event usage is the EventHandler delegate type, which is declared in the System namespace. The following line of code shows the declaration of the EventHandler delegate type. The things to notice about the declaration are the following:

  • The first parameter is meant to hold a reference to the object that raised the event. It is of type object and can, therefore, match any instance of any type.
  • The second parameter is meant to hold state information of whatever type is appropriate for the application.
  • The return type is void.
   public delegate void EventHandler(object sender, EventArgs e);

The second parameter in the EventHandler delegate type is an object of class EventArgs, which is declared in the System namespace. You might be tempted to think that, since the second parameter is meant for passing data, an EventArgs class object would be able to store data of some sort. You would be wrong.

  • The EventArgs class is designed to carry no data. It is used for event handlers that do not need to pass data—and is generally ignored by them.
  • If you want to pass data, you must declare a class derived from EventArgs, with the appropriate fields to hold the data you want to pass.

Even though the EventArgs class does not actually pass data, it is an important part of the pattern of using the EventHandler delegate. These parameters, of types object and EventArgs, are the base classes for whatever actual types are used as the parameters. This allows the EventHandler delegate to provide a signature that is the lowest common denominator for all events and event handlers, allowing all events to have exactly two parameters, rather than having different signatures for each case.

If we modify the Incrementer program to use the EventHandler delegate, we have the program shown in Figure 14-6. Notice the following about the code:

  • The declaration for delegate Handler has been removed since the event uses the system-defined EventHandler delegate.
  • The signature of the event handler declaration in the subscriber class must match the signature (and return type) of the event delegate, which now uses parameters of type object and EventArgs. In the case of event handler IncrementDozensCount, the method just ignores the formal parameters.
  • The code that raises the event must invoke the event with objects of the appropriate parameter types.
Image

Figure 14-6. The Incrementer program modified to use the system-defined EventHandler delegate

Passing Data by Extending EventArgs

To pass data in the second parameter of your event handler and adhere to the standard conventions, you need to declare a custom class derived from EventArgs that can store the data you need passed. The name of the class should end in EventArgs. For example, the following code declares a custom class that can store a string in a field called Message:

                                    Custom class name              Base class
                                                  ↓                                ↓   
   public class IncrementerEventArgs : EventArgs
   {
      public int IterationCount { get; set; }  // Stores an integer
   }

Now that you have a custom class for passing data in the second parameter of your event handlers, you need a delegate type that uses the new custom class. To obtain this, use the generic version of delegate EventHandler<>. Chapter 17 covers C# generics in detail, so for now you'll just have to watch. To use the generic delegate, do the following, as shown in the subsequent code:

  • Place the name of the custom class between the angle brackets.
  • Use the entire string wherever you would have used the name of your custom delegate type. For example, this is what the event declaration would look like:
                                       Generic delegate using custom class
                                                                 ↓                               
   public event EventHandler<IncrementerEventArgs> CountedADozen;
                                                                                                                           ↑             
                                                                                                                   Event name

Use the custom class and the custom delegate in the other four sections of code dealing with the event. For example, the following code updates the Incrementer code to use the custom EventArgs class called IncrementerEventArgs and the generic EventHandler<IncrementerEventArgs> delegate.

   public class IncrementerEventArgs : EventArgs   // Custom class derived from EventArgs
   {
      public int IterationCount { get; set; }      // Stores an integer
   }

   class Incrementer      Generic delegate using custom class
   {                              ↓               
      public event EventHandler<IncrementerEventArgs> CountedADozen;

      public void DoCount()
     Object of custom class
      {
                               ↓     
         IncrementerEventArgs args = new IncrementerEventArgs();
         for ( int i=1; i < 100; i++ )
            if ( i % 12 == 0 && CountedADozen != null )
            {
               args.IterationCount = i;
               CountedADozen( this, args );
            }                     ↑
      }                                      Pass parameters when raising the event
   }

   class Dozens
   {
      public int DozensCount { get; private set; }

      public Dozens( Incrementer incrementer )
      {
         DozensCount = 0;
         incrementer.CountedADozen += IncrementDozensCount;
      }

      void IncrementDozensCount( object source, IncrementerEventArgs e )
      {
         Console.WriteLine( "Incremented at iteration: {0} in {1}",
                                        e.IterationCount, source.ToString() );
         DozensCount++;
      }
   }

   class Program
   {
      static void Main()
      {
         Incrementer incrementer = new Incrementer();
         Dozens dozensCounter    = new Dozens( incrementer );

         incrementer.DoCount();
         Console.WriteLine( "Number of dozens = {0}",
                                 dozensCounter.DozensCount );
      }
   }

This program produces the following output, which displays the iteration when it was called and the fully qualified class name of the source object. I'll cover fully qualified class names in Chapter 21.


Incremented at iteration: 12 in Counter.Incrementer
Incremented at iteration: 24 in Counter.Incrementer
Incremented at iteration: 36 in Counter.Incrementer
Incremented at iteration: 48 in Counter.Incrementer
Incremented at iteration: 60 in Counter.Incrementer
Incremented at iteration: 72 in Counter.Incrementer
Incremented at iteration: 84 in Counter.Incrementer
Incremented at iteration: 96 in Counter.Incrementer
Number of dozens = 8

Removing Event Handlers

When you're done with an event handler, you can remove it from the event. You remove an event handler from an event by using the -= operator, as shown in the following line of code:

   p.SimpleEvent -= s.MethodB;;         // Remove handler MethodB.

For example, the following code adds two handlers to event SimpleEvent, and then raises the event. Each of the handlers is called and prints out a line of text. The MethodB handler is then removed from the event, and when the event is raised again, only the MethodB handler prints out a line.

   class Publisher
   {
      public event EventHandler SimpleEvent;

      public void RaiseTheEvent() { SimpleEvent( this, null ); }
   }

   class Subscriber
   {
      public void MethodA( object o, EventArgs e ) { Console.WriteLine( "AAA" ); }
      public void MethodB( object o, EventArgs e ) { Console.WriteLine( "BBB" ); }
   }

   class Program
   {
      static void Main( )
      {
         Publisher  p = new Publisher();
         Subscriber s = new Subscriber();

         p.SimpleEvent += s.MethodA;
         p.SimpleEvent += s.MethodB;
         p.RaiseTheEvent();

         Console.WriteLine( " Remove MethodB" );
         p.SimpleEvent -= s.MethodB;
         p.RaiseTheEvent();
      }
   }

This code produces the following output:


AAA
BBB

Remove MethodB
AAA

If a handler is registered more than once with an event, then when you issue the command to remove the handler, only the last instance of that handler is removed from the list.

Event Accessors

The last topic to cover in this chapter is event accessors. I mentioned earlier that the += and -= operators are the only operators allowed for an event. These operators have the well-defined behavior that you've seen so far in this chapter.

It is possible, however, to change these operators' behavior and have the event perform whatever custom code you like when they are used. This is an advanced topic, however, so I'll just mention it here, without going into too much detail.

To change the operation of these operators, you must define event accessors for the event.

  • There are two accessors: add and remove.
  • The declaration of an event with accessors looks similar to the declaration of a property.

The following example shows the form of an event declaration with accessors. Both accessors have an implicit value parameter called value that takes a reference to either an instance method or a static method.

   public event EventHandler CountedADozen
   {
      add
      {
         ...                            // Code to implement the =+ operator
      }

      remove
      {
         ...                            // Code to implement the -= operator
      }
   }

When event accessors are declared, the event does not contain an embedded delegate object. You must implement your own storage mechanism for storing and removing the methods registered with the event.

The event accessors act as void methods, meaning that they cannot use return statements that return a value.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.171.212