Events

Events

An event is an occurrence that an object or class wants to notify someone else about. (The someone else is either another object or class.) A button notifies a form object that someone clicked on the button. A timer notifies some other object that a time period has elapsed. An application domain notifies an application of an unhandled event. A Web application notifies an object of an application event. You can create custom events, such as a device notifying a monitoring application that data transmission has started or stopped. This list of events is somewhat eclectic, which underscores that there is no strict definition. Events are literally anything worth notifying someone else about. Public events are exposed as public members of a publisher class, such as a button class. You can also have private and protected events that are used within the realm of the containing or derived class.

Any object or class interested in an event can subscribe. Subscribers register for an event by submitting a delegate. The delegate must be single cast and contain a single function pointer. That function is the subscriber's response to the event. When the event is raised, the publisher calls the function, affording the subscriber an opportunity to respond to the event. For this reason, the function is called an event handler. Events can have multiple subscribers. A device could publish a power-off event in which multiple applications might want to be notified and respond.

What happens when there are no subscribers to a particular event? In other words, if a tree falls in the woods and no one hears it, does it make a sound? If the tree is an event, no sound is made. Events that have no subscribers are not raised.

Publishing an Event

Both classes and objects can publish events. Static events are published at classes, whereas objects publish instance events. Multicast delegates underlie events. You must create a delegate for the event or leverage a predefined event. Events are defined with the event keyword. This is the syntax for defining an event:

  • accessibility event delegatename eventname

Events are defined as a field in a class. The accessibility of an event is typically public or protected. Protected events restrict subscribers to child objects. A public event is available to every interested subscriber. Delegate name is the underlying delegate of the event, which defines the signature of the event. Event name is the identity of the event.

public delegate void DelegateClass();
public event DelegateClass MyEvent;

As a best practice, event handlers should return void and have two arguments. EventHandler is a predefined delegate created for this purpose. It is included in the .NET Framework class library (FCL) and prevents you from having to declare a separate delegate for the standard event type. The first parameter is the object that raised the event. The second parameter is an EventArgs derived instance, which offers optional data that further explains the event.

Internally, events declared in a class become a field of that class. Delegates used to define the event, which is a multicast delegate, are types used for the field. The C# compiler also inserts methods to add and remove subscribers to the event. The names of the methods are add_EventName and remove_EventName, respectively. The fourth member added to the class is the event itself. See Figure 8-5 for a disassembly of an event class.

Image from book
Figure 8-5: Disassembly of the event class

Subscribe

The publisher/subscriber relationship is a one-to-many relationship. For each publisher there can be zero or more subscribers. Conversely, a subscriber can subscribe to multiple events. For example, a form can subscribe to a button click and a text change event. Subscribers add a delegate to an event to register for the event. The delegate is a wrapper for the function to be called when the event is raised. Subscribe to an event using the add method or the += assignment operator:

using System;

namespace Donis.CSharpBook{
     class Publisher {
         public event EventHandler MyEvent;
     }

     public class Subscriber{

         public static void Handler(object obj, EventArgs args) {
         }

         public static void Main(){
             Publisher pub=new Publisher();
             pub.MyEvent+=Handler;
             // other processing
         }
     }
}

Cancel a subscription to a delegate with the -= assignment operator.

Raising an Event

At the discretion of a publisher, an event is raised and the methods of the subscribers are called back. Raise an event with the call operator "()". Adding the call operator to the event raises the event. Because a delegate underlies an event, the Invoke method inherited from a multicast delegate is another means to raising an event. The signature of both the call operator and the Invoke method match the delegate of the event. Any parameters are passed to the called methods of the subscribers. If an event returns a value as defined by the event delegate, the function of the last subscriber sets the return. Do not raise an event for events without subscribers. Events with no subscribers are null, which can be tested. An application that raises an event that has no subscribers will incur an exception because of a null reference. This is the proper way to raise an event:

 public void SomeMethod() {
         if(anEvent!=null) {
             anEvent(null, null);
         }
 }

EventArgs

Events sometime provide subscribers with custom information pertaining to the event. The mouse click event provides the x and y coordinates of the mouse pointer in the MouseEventArgs class. The DataRowChangeEventArgs class provides the affected row and action of several database-related events, such as the RowChanged event. The PaintEventArgs class is instantiated in a paint event. It gives developers the clip rectangle and graphics object for the paint event. Custom information about an event is defined in an EventArgs derived class. MouseEventArgs, DataRowChangeEventArgs, PaintEventArgs, and other related classes derive from the EventArgs class. These classes represent the state information for the event.

The EventArgs derived class that contains the state information of the event is typically passed as the second parameter of the event. State information can also be specified in the other parameters of events.

This is code for a bank account. The NSF event is raised when the account is overdrawn. The BankEventArgs class provides the bank account balance and amount of the transaction that would overdraw the account.

using System;

namespace Donis.CSharpBook{

     public class Starter {
         public static void Main(){
             Bank account=new Bank();
             account.NSF+=NSFHandler;
             account.Deposit(500);
             account.Withdrawal(750);
         }

         public static void NSFHandler(object o,
                 BankEventArgs e) {
             Console.WriteLine("NSF Transaction");
             Console.WriteLine("Balance: {0}", e.Balance);
             Console.WriteLine("Transaction: {0}",
                 e.Transaction);
         }

     }

     public delegate void OverDrawn(object o, BankEventArgs e);
     public class Bank {
         public event OverDrawn NSF; // non sufficient funds

         public decimal Deposit(decimal amountDeposit) {
             propBalance+=amountDeposit;
             return propBalance;
         }

         public decimal Withdrawal(decimal amountWithdrawn) {
             decimal newBalance=propBalance-amountWithdrawn;
             if(newBalance < 0) {
                 if(NSF != null) {
                     BankEventArgs args=new BankEventArgs(
                         Balance, amountWithdrawn);
                     NSF(this, args);
                 }
                 return propBalance;
             }
             return propBalance=newBalance;
         }
         private decimal propBalance=0;
         public decimal Balance {
             get {
                 return propBalance;
             }
         }

     }

     public class BankEventArgs: EventArgs {

         public BankEventArgs(decimal amountBalance,
                 decimal amountTransaction) {
             propBalance=amountBalance;
             propTransaction=amountTransaction;
         }

         private decimal propBalance;
         public decimal Balance {
             get {
                 return propBalance;
             }
         }

         private decimal propTransaction;
         public decimal Transaction {
             get {
                 return propTransaction;
             }
         }
     }
}