Events


Built upon the foundation of delegates is another important C# feature: the event. An event is, essentially, an automatic notification that some action has occurred. Events work like this: An object that has an interest in an event registers an event handler for that event. When the event occurs, all registered handlers are called. Event handlers are represented by delegates.

Events are members of a class and are declared using the event keyword. Its most commonly used form is shown here:

 event event-delegate object-name;

Here, event-delegate is the name of the delegate used to support the event, and object-name is the name of the specific event object being created.

Let’s begin with a very simple example:

 // A very simple event demonstration. using System; // Declare a delegate for an event. delegate void MyEventHandler(); // Declare an event class. class MyEvent {   public event MyEventHandler SomeEvent;   // This is called to fire the event.   public void OnSomeEvent() {     if(SomeEvent != null)       SomeEvent();   } } class EventDemo {   // An event handler.   static void handler() {     Console.WriteLine("Event occurred");   }   public static void Main() {     MyEvent evt = new MyEvent();     // Add handler() to the event list.     evt.SomeEvent += handler; // use method group conversion     // Fire the event.     evt.OnSomeEvent();   } }

This program displays the following output:

 Event occurred

Although simple, this program contains all the elements essential to proper event handling. Let’s look at it carefully. The program begins by declaring a delegate for the event handler, as shown here:

 delegate void MyEventHandler();

All events are activated through a delegate. Thus, the event delegate defines the return type and signature for the event. In this case, there are no parameters, but event parameters are allowed. Because events are commonly multicast, an event will normally return void.

Next, an event class, called MyEvent, is created. Inside the class, an event object called SomeEvent is declared, using this line:

 public event MyEventHandler SomeEvent;

Notice the syntax. The keyword event tells the compiler that an event is being declared. This is the way that all types of events are declared.

Also declared inside MyEvent is the method OnSomeEvent( ), which is the method that a program will call to signal (or “fire”) an event. (That is, this is the method called when the event occurs.) It calls an event handler through the SomeEvent delegate, as shown here:

 if(SomeEvent != null)   SomeEvent();

Notice that a handler is called if and only if SomeEvent is not null. Since other parts of your program must register an interest in an event in order to receive event notifications, it is possible that OnSomeEvent( ) could be called before any event handler has been registered. To prevent calling a null object, the event delegate must be tested to ensure that it is not null.

Inside EventDemo, an event handler called handler( ) is created. In this simple example, the event handler just displays a message, but other handlers could perform more meaningful actions. In Main( ), a MyEvent object is created, and handler( ) is registered as a handler for this event by adding it as shown here:

 MyEvent evt = new MyEvent(); // Add handler() to the event list. evt.SomeEvent += handler; // use method group conversion

Notice that the handler is added using the += operator. Events support only += and – =. In this case, handler( ) is a static method, but event handlers can also be instance methods. Notice that the new method group conversion syntax can be used with delegates that support events. In the past, the handler would have been added by this line:

 evt.SomeEvent += new MyEventHandler(handler); // old style

Finally, the event is fired as shown here:

 // Fire the event. evt.OnSomeEvent();

Calling OnSomeEvent( ) causes all registered event handlers to be called. In this case, there is only one registered handler, but there could be more, as the next section explains.

A Multicast Event Example

Like delegates, events can be multicast. This enables multiple objects to respond to an event notification. Here is an event multicast example:

 // An event multicast demonstration. using System; // Declare a delegate for an event. delegate void MyEventHandler(); // Declare an event class. class MyEvent {   public event MyEventHandler SomeEvent;   // This is called to fire the event.   public void OnSomeEvent() {     if(SomeEvent != null)       SomeEvent();   } } class X {   public void Xhandler() {     Console.WriteLine("Event received by X object");   } } class Y {   public void Yhandler() {     Console.WriteLine("Event received by Y object");   } } class EventDemo2 {   static void handler() {     Console.WriteLine("Event received by EventDemo");   }   public static void Main() {     MyEvent evt = new MyEvent();     X xOb = new X();     Y yOb = new Y();     // Add handlers to the event list.     evt.SomeEvent += handler;     evt.SomeEvent += xOb.Xhandler;     evt.SomeEvent += yOb.Yhandler;     // Fire the event.     evt.OnSomeEvent();     Console.WriteLine();     // Remove a handler.     evt.SomeEvent -= xOb.Xhandler;     evt.OnSomeEvent();   } }

The output from the program is shown here:

 Event received by EventDemo Event received by X object Event received by Y object Event received by EventDemo Event received by Y object

This example creates two additional classes, called X and Y, which also define event handlers compatible with MyEventHandler. Thus, these handlers can also become part of the event chain. Notice that the handlers in X and Y are not static. This means that objects of each must be created, and the handler linked to each object instance must be added to the event chain. The differences between instance and static handlers are examined in the next section.

Instance Methods vs. static Methods as Event Handlers

Although both instance methods and static methods can be used as event handlers, they do differ in one important way. When a static method is used as a handler, an event notification applies to the class (and implicitly to all objects of the class). When an instance method is used as an event handler, events are sent to specific object instances. Thus, each object of a class that wants to receive an event notification must register individually. In practice, most event handlers are instance methods, but, of course, this is subject to the specific application. Let’s look at an example of each.

The following program creates a class called X that defines an instance method as an event handler. This means that each X object must register individually to receive events. To demonstrate this fact, the program multicasts an event to three objects of type X.

 /* Individual objects receive notifications when instance    event handlers are used. */ using System; // Declare a delegate for an event. delegate void MyEventHandler(); // Declare an event class. class MyEvent {   public event MyEventHandler SomeEvent;   // This is called to fire the event.   public void OnSomeEvent() {     if(SomeEvent != null)       SomeEvent();   } } class X {   int id;   public X(int x) { id = x; }   // This is an instance method that will be used as an event handler.   public void Xhandler() {     Console.WriteLine("Event received by object " + id);   } } class EventDemo3 {   public static void Main() {     MyEvent evt = new MyEvent();     X o1 = new X(1);     X o2 = new X(2);     X o3 = new X(3);     evt.SomeEvent += o1.Xhandler;     evt.SomeEvent += o2.Xhandler;     evt.SomeEvent += o3.Xhandler;     // Fire the event.     evt.OnSomeEvent();   } }

The output from this program is shown here:

 Event received by object 1 Event received by object 2 Event received by object 3

As the output shows, each object registers its interest in an event separately, and each receives a separate notification.

Alternatively, when a static method is used as an event handler, events are handled independently of any object, as the following program shows:

 /* A class receives the notification when    a static method is used as an event handler. */ using System; // Declare a delegate for an event. delegate void MyEventHandler(); // Declare an event class. class MyEvent {   public event MyEventHandler SomeEvent;   // This is called to fire the event.   public void OnSomeEvent() {     if(SomeEvent != null)       SomeEvent();   } } class X {   /* This is a static method that will be used as      an event handler. */   public static void Xhandler() {     Console.WriteLine("Event received by class.");   } } class EventDemo4 {   public static void Main() {     MyEvent evt = new MyEvent();     evt.SomeEvent += X.Xhandler;     // Fire the event.     evt.OnSomeEvent();   } }

The output from this program is shown here:

 Event received by class.

In the program, notice that no object of type X is ever created. However, since handler( ) is a static method of X, it can be attached to SomeEvent and executed when OnSomeEvent( ) is called.

Using Event Accessors

There are two forms of the event statement. The form used in the preceding examples created events that automatically manage the event handler invocation list, including the adding and subtracting of event handlers to and from the list. Thus, you did not need to implement any of the list management functionality yourself. Because they manage the details for you, these types of events are by far the most commonly used. It is possible, however, to provide the event handler list operations yourself, perhaps to implement some type of specialized event storage mechanism.

To take control of the event handler list, you will use the second form of the event statement, which allows the use of event accessors. The accessors give you control over how the event handler list is implemented. This form is shown here:

 event event-delegate event-name {    add {       // code to add an event to the chain    }    remove {       // code to remove an event from the chain    } }

This form includes the two event accessors add and remove. The add accessor is called when an event handler is added to the event chain, by using +=. The remove accessor is called when an event handler is removed from the chain, by using =.

When add or remove is called, it receives the handler to add or remove as a parameter. As with other types of accessors, this parameter is called value. By implementing add and remove, you can define a custom event-handler storage scheme. For example, you could use an array, a stack, or a queue to store the handlers.

Here is an example that uses the accessor form of event. It uses an array to hold the event handlers. Because the array is only three elements long, only three event handlers can be held in the chain at any one time.

 // Create a custom means of managing the event invocation list. using System; // Declare a delegate for an event. delegate void MyEventHandler(); // Declare an event class that holds up to 3 events. class MyEvent {   MyEventHandler[] evnt = new MyEventHandler[3];   public event MyEventHandler SomeEvent {     // Add an event to the list.     add {       int i;       for(i=0; i < 3; i++)         if(evnt[i] == null) {           evnt[i] = value;           break;         }       if (i == 3) Console.WriteLine("Event list full.");     }     // Remove an event from the list.     remove {       int i;       for(i=0; i < 3; i++)         if(evnt[i] == value) {           evnt[i] = null;           break;         }       if (i == 3) Console.WriteLine("Event handler not found.");     }   }   // This is called to fire the events.   public void OnSomeEvent() {       for(int i=0; i < 3; i++)         if(evnt[i] != null) evnt[i]();   } } // Create some classes that use MyEventHandler. class W {   public void Whandler() {     Console.WriteLine("Event received by W object");   } } class X {   public void Xhandler() {     Console.WriteLine("Event received by X object");   } } class Y {   public void Yhandler() {     Console.WriteLine("Event received by Y object");   } } class Z {   public void Zhandler() {     Console.WriteLine("Event received by Z object");   } } class EventDemo5 {   public static void Main() {     MyEvent evt = new MyEvent();     W wOb = new W();     X xOb = new X();     Y yOb = new Y();     Z zOb = new Z();     // Add handlers to the event list.     Console.WriteLine("Adding events.");     evt.SomeEvent += wOb.Whandler;     evt.SomeEvent += xOb.Xhandler;     evt.SomeEvent += yOb.Yhandler;     // Can't store this one -- full.     evt.SomeEvent += zOb.Zhandler;     Console.WriteLine();     // Fire the events.     evt.OnSomeEvent();     Console.WriteLine();     // Remove a handler.     Console.WriteLine("Remove xOb.Xhandler.");     evt.SomeEvent -= xOb.Xhandler;     evt.OnSomeEvent();     Console.WriteLine();     // Try to remove it again.     Console.WriteLine("Try to remove xOb.Xhandler again.");     evt.SomeEvent -= xOb.Xhandler;     evt.OnSomeEvent();     Console.WriteLine();     // Now, add Zhandler.     Console.WriteLine("Add zOb.Zhandler.");     evt.SomeEvent += zOb.Zhandler;     evt.OnSomeEvent();   } }

The output from the program is shown here:

 Adding events. Event list full. Event received by W object Event received by X object Event received by Y object Remove xOb.Xhandler. Event received by W object Event received by Y object Try to remove xOb.Xhandler again. Event handler not found. Event received by W object Event received by Y object Add zOb.Zhandler. Event received by W object Event received by Z object Event received by Y object

Let’s examine this program closely. First, an event handler delegate called MyEventHandler is defined. Next, the class MyEvent is declared. It begins by defining a three-element array of event handlers called evnt, as shown here:

 MyEventHandler[] evnt = new MyEventHandler[3];

This array will be used to store the event handlers that are added to the event chain. The elements in evnt are initialized to null by default.

Next, the event SomeEvent is declared. It uses the accessor form of the event statement, as shown here:

 public event MyEventHandler SomeEvent {   // Add an event to the list.   add {     int i;     for(i=0; i < 3; i++)       if(evnt[i] == null) {         evnt[i] = value;         break;       }     if (i == 3) Console.WriteLine("Event list full.");   }   // Remove an event from the list.   remove {     int i;     for(i=0; i < 3; i++)       if(evnt[i] == value) {         evnt[i] = null;         break;       }     if (i == 3) Console.WriteLine("Event handler not found.");   } }

When an event handler is added, add is called and a reference to the handler (contained in value) is put into the first unused (that is, null) element of evnt. If no element is free, then an error is reported. (Of course, throwing an exception when the list is full would be a better approach for real-world code.) Since evnt is only three elements long, only three event handlers can be stored. When an event handler is removed, remove is called and the evnt array is searched for the reference to the handler passed in value. If it is found, its element in the array is assigned null, thus removing the handler from the list.

When an event is fired, OnSomeEvent( ) is called. It cycles through the evnt array, calling each event handler in turn.

As the preceding example shows, it is relatively easy to implement a custom event-handler storage mechanism if one is needed. For most applications, though, the default storage provided by the non-accessor form of event is better. The accessor-based form of event can be useful in certain specialized situations, however. For example, if you have a program in which event handlers need to be executed in order of their priority and not in the order in which they are added to the chain, then you could use a priority queue to store the handlers.

Miscellaneous Event Features

Events can be specified in interfaces. Implementing classes must supply the event.

Events can be specified as abstract. A derived class must implement the event. Accessor-based events cannot, however, be abstract.

An event can be specified as sealed.

An event can be virtual, which means that it can be overridden in a derived class.




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

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