An event represents a signal that something noteworthy has happened in a program. Examples of events are a button click, a key press, a timer lapsing one minute, and a printer finishing printing. An object with the ability to generate events is called a publisher and is a source of events. To generate an event is also called to fire an event.

One or more objects can subscribe (or register) to be notified about a certain event fired from another object. These objects are called subscribers or listeners. Each subscriber must contain a method called an event handler to handle the event the subscriber has subscribed to be notified about. All subscribing event handlers are invoked one after the other when the corresponding event is fired. A subscriber can subscribe to several different event types and can contain several event handlers. Multicast delegates are highly suited to implement the event handling process and, thus, to form a link between subscribers and publishers.

What is the advantage of separating certain programs into publishers and subscribers? Why can't the publishers just react to the events themselves? Event handling allows us to write programs with lower coupling. The programmers writing the publisher objects don't need to know much about the subscriber objects and vice versa. Furthermore, subscriber type objects can easily subscribe or unsubscribe to certain events during runtime because this aspect has not been hardwired in the code.

Writing Event-Driven Programs

An event in C# is nothing more than a slightly specialized multicast delegate, tailor-made for the event handling process. As a result, events are easier to use and more robust for this purpose than normal multicast delegates.



The following discussion refers to the delegates, events and event handlers from Listing 20.4 displayed later. The line numbers are provided as a quick reference to this listing.

An event is declared by applying the event keyword to a multicast delegate. For example, if we define the following multicast delegate called MoveRequest (line 25) (you can ignore the two parameters sender and e for now, they are discussed later)

 public delegate void MoveRequest(object sender, MoveRequestEventArgs e); 

then, instead of declaring a normal multicast delegate called MyMoveRequest, say, as shown next

 public MoveRequest MyMoveRequest; 

we can add the event keyword as shown in the following line (line 27) to declare an event called OnMoveRequest

 public event MoveRequest OnMoveRequest; 

OnMoveRequest is so similar to a normal multicast delegate like MyMoveRequest that you, for most purposes, can regard it as being a multicast delegate.



Any event name by convention starts with On.

The delegate definition and its associated event declaration are positioned in the publisher class (the GameController class in lines 23 99). The event handler for this delegate and event is positioned in the subscribing class (the Car class in lines 101 147). As usual, the event handler must have the same return value and formal parameter types as the delegate (MoveRequest, in this case). This enables the event handler to be encapsulated by the event OnMoveRequest (by subscribing to this event), and it will consequently be called when the event is fired.



Firing an event in C# is done by calling the event, just as when we call a delegate, as shown in the previous sections. In this case, it would mean to execute a line similar to the following (see lines 81, 85 and 89) inside the publisher:

 OnMoveRequest(senderObject, someEventArguments); 

In our case, the event handler could have the following header (line 124):

 public void MoveRequestHandler(object sender, MoveRequestEventArgs e) {     ... } 

Suppose the delegate and the event it implements are defined in a class called GameController (lines 23 99) and are currently sitting in an instance of GameController called controller. It is now possible for the subscriber to subscribe to the event by encapsulating its MoveRequestHandler (event handler) method in the OnMoveRequest event in the familiar multicast fashion (line 116):

 controller.OnMoveRequest += new GameController.MoveRequest(MoveRequestHandler); 

In a similar fashion, the MoveRequestHandler can unsubscribe to the event by being un-encapsulated, as in the following line (line 121):

 controller.OnMoveRequest -= new GameController.MoveRequest(MoveRequestHandler); 



One of the major differences between a normal multicast delegate and an event is that only the operators += and -= can be used with an event from outside the object in which it resides. For example, the following line is invalid because OnMoveRequest is an event::

 controller.OnMoveRequest = new GameController.MoveRequest( Â MoveRequestHandler);    //Invalid 

This is a good thing because this line would cause havoc by removing all other event handlers encapsulated by OnMoveRequest and, instead encapsulate just the one MoveRequestHandler method specified in the line. If this statement was valid, one subscriber could effectively wipe out all other subscriptions.

When the compiler encounters the event keyword, it implicitly declares this event private and creates two public properties (only visible in the MSIL) called add_<Event_name> and remove_<Event_name> that are accessible with the += and -= operators.

For a moment, we need to return to the parameters (object sender and MoveRequestEventArgs e) of the MoveRequest delegate with which our event is implemented. Delegates that events are implemented with always have, per convention, two parameters of type System.Object (alias is object in C#) and type System.EventArgs (or a subclass thereof) as shown here (where MoveRequestEventArgs is a subclass of System.EventArgs):

 public delegate void MoveRequest(object sender, MoveRequestEventArgs e); 

The object parameter allows the publishing object to inform the subscribing object's event handler where (in what object) the event was fired (and is therefore often called sender). The event is fired from the object itself. A reference to the object itself is found in the this keyword, which, consequently, is the first argument, as in the following line (see line 81):

 OnMoveRequest(this, new MoveRequestEventArgs(MoveRequestType.FastForward)); 

The second parameter, MoveRequestEventArgs (subclass of System.EventArgs), allows the publisher to pass particular information along to the subscriber about the event. To fully explain and exemplify the use of this parameter, we need to look closer at Listing 20.4. We do this in the analysis after the sample output.

Listing 20.4 CarGameEvents.cs
 01: using System;  02:  03: enum MoveRequestType {FastForward, SlowForward, Reverse} ;  04:  05: class MoveRequestEventArgs : EventArgs  06: {  07:     private MoveRequestType request;  08:  09:     public MoveRequestEventArgs(MoveRequestType initRequest) : base()  10:     {  11:         request = initRequest;  12:     }  13:  14:     public MoveRequestType Request  15:     {  16:         get  17:         {  18:             return request;  19:         }  20:     }  21: }  22:  23: class GameController  24: {  25:     public delegate void MoveRequest(object sender, MoveRequestEventArgs e);  26:  27:     public event MoveRequest OnMoveRequest;  28:  29:     Car[] gameCars = new Car[10];  30:     string carName;  31:     int speedParam = 0;  32:     int carCounter = 0;  33:     int carNumber = 0;  34:  35:     public void Run()  36:     {  37:         string answer;  38:         Console.WriteLine("Please select from the following menu: ");  39:         Console.WriteLine("A)dd new car");  40:         Console.WriteLine("C)ar. Subscribe to events");  41:         Console.WriteLine("U)nsubscribe from events");  42:         Console.WriteLine("L)ist cars in current game");  43:         Console.WriteLine("F)ast forward");  44:         Console.WriteLine("S)low forward");  45:         Console.WriteLine("R)everse");  46:         Console.WriteLine("T)erminate");  47:  48:         do  49:         {  50:             Console.WriteLine("Select new option:");  51:             answer = Console.ReadLine().ToUpper();  52:  53:             switch(answer)  54:             {  55:                 case "A":  56:                     Console.Write("Enter name of the new car: ");  57:                     carName = Console.ReadLine();  58:                     Console.Write("Enter car speed parameter of the new car: ");  59:                     speedParam = Convert.ToInt32(Console.ReadLine());  60:                     gameCars[carCounter] = new Car(speedParam, carName);  61:                     carCounter++;  62:                 break;  63:                 case "C":  64:                     Console.Write("Enter array index of car you want to subscribe to  graphics/ccc.gifevents: ");  65:                     carNumber = Convert.ToInt32(Console.ReadLine());  66:                     gameCars[carNumber].Subscribe(this);  67:                 break;  68:                 case "U":  69:                     Console.Write("Enter array index of car you want to unsubscribe  graphics/ccc.giffrom events: ");  70:                     carNumber = Convert.ToInt32(Console.ReadLine());  71:                     gameCars[carNumber].Unsubscribe(this);  72:                 break;  73:                 case "L":  74:                     for(int i=0; i < carCounter; i++)  75:                     {  76:                         Console.WriteLine(gameCars[i]);  77:                     }  78:                 break;  79:                 case "F":  80:                     if (OnMoveRequest != null)  81:                         OnMoveRequest(this, new MoveRequestEventArgs  graphics/ccc.gif(MoveRequestType.FastForward));  82:                 break;  83:                 case "S":  84:                     if (OnMoveRequest != null)  85:                         OnMoveRequest(this, new MoveRequestEventArgs  graphics/ccc.gif(MoveRequestType.SlowForward));  86:                 break;  87:                 case "R":  88:                     if (OnMoveRequest != null)  89:                         OnMoveRequest(this, new MoveRequestEventArgs  graphics/ccc.gif(MoveRequestType.Reverse));  90:                 break;  91:                 case "T":  92:                 break;  93:                 default:  94:                     Console.WriteLine("Invalid choice. Please try again");  95:                 break;  96:             }  97:         }  while(answer != "T");  98:     }  99: } 100: 101: class Car 102: { 103:     private int distance; 104:     private int speedParam; 105:     private string name; 106: 107:     public Car(int initSpeedParam, string initName) 108:     { 109:         speedParam = initSpeedParam; 110:         distance = 0; 111:         name = initName; 112:     } 113: 114:     public void Subscribe(GameController controller) 115:     { 116:         controller.OnMoveRequest += new GameController.MoveRequest  graphics/ccc.gif(MoveRequestHandler); 117:     } 118: 119:     public void Unsubscribe(GameController controller) 120:     { 121:         controller.OnMoveRequest -= new GameController.MoveRequest  graphics/ccc.gif(MoveRequestHandler); 122:     } 123:  124:     public void MoveRequestHandler(object sender, MoveRequestEventArgs e) 125:     { 126:         switch (e.Request) 127:         { 128:             case MoveRequestType.SlowForward: 129:                 distance += speedParam; 130:                 Console.WriteLine("Car name: " + name + " Moving slowly. Distance: "  graphics/ccc.gif+ distance); 131:             break; 132:             case MoveRequestType.FastForward: 133:                 distance += speedParam * 2; 134:                 Console.WriteLine("Car name: " + name + " Moving fast. Distance: " +  graphics/ccc.gifdistance); 135:             break; 136:             case MoveRequestType.Reverse: 137:                 distance -= 5; 138:                 Console.WriteLine("Car name: " + name + " Reversing. Distance: " +  graphics/ccc.gifdistance); 139:             break; 140:         } 141:     } 142: 143:     public override string ToString() 144:     { 145:         return name; 146:     } 147: } 148: 149: class Tester 150: { 151:     public static void Main() 152:     { 153:         GameController controller = new GameController(); 154:         controller.Run(); 155:     } 156: } Please select from the following menu: A)dd new car C)ar. Subscribe to events U)nsubscribe from events L)ist cars in current game F)ast forward S)low forward R)everse T)erminate Select new option: A<enter> Enter name of the new car: Volvo<enter> Enter car speed parameter of the new car: 20<enter> Select new option: F<enter> Select new option: C<enter> Enter array index of car you want to subscribe to events: 0 Select new option: F<enter> Car name: Volvo Moving fast. Distance: 40 Select new option: A<enter> Enter name of the new car: Lotus<enter> Enter car speed parameter of the new car: 40<enter> Select new option: C<enter> Enter array index of car you want to subscribe to events: 1<enter> Select new option: S<enter> Car name: Volvo Moving slowly. Distance: 60 Car name: Lotus Moving slowly. Distance: 40 Select new option: U<enter> Enter array index of car you want to unsubscribe from events: 0<enter> Select new option: R<enter> Car name: Lotus Reversing. Distance: 35 Select new option: L<enter> Volvo Lotus Select new option: C<enter> Enter array index of car you want to subscribe to events: 0<enter> Select new option: R<enter> Car name: Lotus Reversing. Distance: 30 Car name: Volvo Reversing. Distance: 55 Select new option: T<enter> 

Listing 20.4 is an event-driven car game. Overall, it simply contains a list of cars that can move forward or backward. Their movements are controlled by an instance of the GameController class, which again is controlled by the end user. Even though simplified, the game has the same overall architecture as most event-driven programs.

The GameController class publishes the event OnMoveRequest (line 27), to which one or more Car objects can subscribe. A subscribing Car object will have its event handler invoked (lines 124 141) when OnMoveRequest is fired. The event handler will, depending on the content of the MoveRequestEventArgs (line 124) parameter, either move the Car a certain distance forward (the actual amount is determined by its speedParam instance variable, see lines 104, 109, 129 and 133) or reverse the Car 5 kilometers (line 137). To pass along the necessary information, MoveRequestEventArgs is defined in lines 5 21, to contain the instance variable request of type MoveRequestType, which is an enum defined in line 3. Consequently, request can contain one of the three values MoveRequestType.FastForward, MoveRequestType.SlowForward, or MoveRequestType.Reverse. One of these values is assigned to the new MoveRequestEventArgs instance in lines 81, 85 or 89, and, through the switch statement in Car's event handler (lines 126, 128, 132 and 136), controls the particular action taken by the event handler. As required, MoveRequestEventArgs is derived from System.EventArgs (line 9).

The array gameCars (line 29) can hold ten Car objects. Car objects can be added (lines 55 61), subscribed to events (lines 63 66), un-subscribed from events (lines 68 71) and current Car objects be listed (lines 73 77). Notice from the sample output that only Car objects that subscribe to the event are moved.

The program does not check the array indexes you provide and does not provide any exception handling for out of range exceptions.

Observe that other classes, such as Motorbike, Pedestrian, and so on, could easily be written and added to the program and, if equipped with a suitable event handler, become active game participants by subscribing to the OnMoveRequest event.


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: