Understanding C# Events

The C# event Keyword

As a shortcut, so you do not have to build custom methods to add or remove methods to a delegate's invocation list, C# provides the "event" keyword.


When the compiler processes the "event" keyword, you are automatically provided with registration and un-registration methods, as well as necessary member variables for your delegate types. These delegate member variables are always declared private, and, therefore, they are not directly exposed from the object firing the event.


Defining an event is 2 step process:

  1. You need to define a delegate type that will hold the list of methods to be called when the event is fired.
  2. You declare and event (using the C# "event" keyword) in terms of the related delegate type.


// This car send these events.
public event CarEngineHandler Exploded;
public event CarEngineHandler AboutToBlow;
 
// Implement the Accelerate() method
public void Accelerate(int delta)
 {
     if (_carIsDead)
     {
         Exploded.Invoke("Sorry, this car is dead!");
     }else
     {
         CurrentSpeed += delta;
         // Almost dead!
         if (10 == (MaxSpeed - CurrentSpeed))
         {
             AboutToBlow.Invoke("Careful Buddy!");
         } 
         if (CurrentSpeed >= MaxSpeed)
         {
             _carIsDead = true;
         }else
         {
             Console.WriteLine($"Current speed {CurrentSpeed}");
         }
     }
 }


Events Under the Hood

When the compiler processes the C# "event" keyword, it generates 2 hidden methods, one having an "add_" prefix and the other having a "remove_" prefix. Each prefix is followed by the name of the C# event.


// Ex :
// Exploded event has 2 hidden methods.
// add_Exploded()    { 
// remove_Exploded() { 

Listening to Incoming Events

C# events also simplify the act of registering the caller-side event handlers. Rather than having to specify custom helper methods, the caller simply uses the += and -= operators directly (which triggers the correct add_XXX() or remove_XXX method in the background). When you wan to register with an event, follow the pattern shown here:


// NameOfObject.NameOfEvent += new RelatedDelegate(functionToCall)
Car.CarEngineHandler d = new Car.CarEngineHandler(CarExplodedEventHandler);
c2.Exploded += d;
 
When you want to detach from a source of events, use the -= operator, like:
// NameOfObject.NameOfEvent -= new RelatedDelegate(functionToCall)
c2.Exploded -= d;
 
Note that you can also use the method group conversion syntax with events as well:
 
c1.AboutToBlow += CarIsAlmosDoomed;
c1.AboutToBlow += CarAboutToBlow;
Car.CarEngineHandler d = CarExploded;
c1.Exploded += d;
 
 
private static void CarExploded(string msgForCaller)
 {
     throw new NotImplementedException();
 }
private static void CarAboutToBlow(string msgForCaller)
 {
     throw new NotImplementedException();
 }
private static void CarIsAlmosDoomed(string msgForCaller)
 {
     throw new NotImplementedException();
 }


Creating Custom Event Arguments

  1. Create Class that extends EventArgs ->holds your custom data.
  2. Delegate Signature : (object sender, YourEventArgs e)
  3. sender = who fired the event (cast to original type to use)
  4. e = data package (message,speed,status,etc.)


public class CarEventArgs : EventArgs
 {
     private readonly string msg;
     public CarEventArgs(string message)
     {
         msg = message;
     }
 }

class Car
 {
      public delegate void CarEngineHandler(object sender, CarEventArgs e);
....
public void Accelerate(int delta)
 {
     if (_carIsDead)
     {
         Exploded.Invoke(this,new CarEventArgs("Sorry this car is dead"));
     }else
....



The Generic EventHandler<T> Delegate

Given that so many custom delegates take an object as the first parameter and an EventArgs descendant as the second, you could further streamline the previous example by using the generic EventHandler<T> type, where T is your custom EventArgs type.


public event EventHandler<CarEventArgs> Exploded;
public event EventHandler<CarEventArgs> AboutToBlow;