I often end up confusing myself when building events in .NET. I understand the concepts well enough, but the .NET conventions for the elements that go into setting up and handling an event seem counter-intuitive to me. So today I will layout the components of an event so that I can follow them in the future.
I will use the model of a vehicle object and a traffic light object. The event being modeled is the vehicle’s reaction to the traffic light changing colour.
First, we build a traffic light (stoplight).
- stoplight has a method that changes the colour of the light (CycleColour()) (this is the main method that we want to have our cars react to).
- stoplight has an event object (SignalChanged) which allows us to tell stoplight which object.methods should be called when the signal changes.
- note: the naming convention for the type is {eventtype}Handler (e.g. SignalChangedHandler), but this object is not a handler – handlers will be registered later. The {eventtype}Handler object is actually a collection object holding multiple handlers. Hence my earlier comment about .NET’s counter-intuitive conventions. A more intuive naming convention would be {eventtype}Handlers.
- Whenever the traffic light changes a call is made to OnSignalChanged()
- in .NET-speak the SignalChanged event is raised.
- Again, note that the naming convenition On{eventname} which implies it is a reactive method, but it is really an initiating method – a more intuitive name would be Trigger{eventname}Event or Raise{eventname}Event.
- When raising the event, we pass it a custom-made object (TrafficLightEventsArgs) that can hold information about who/what/why/when/where the event was raised.
- When the event is raised, OnSignalChanged() performs what ever it needs to and notifies any listeners that the event has occured (the SignalChanged event is invoked).
We also build two vehicles (car and truck).
- The Vehicle class has two methods that can handle a SignalChanged event (ObeyTheLight() and WarnPedestrians()). These methods can accept the custom information that will be forwarded from stoplight and perform whatever tasks are required to be done when notified of a SignalChanged event (they consume the event).
- Finally car and truck each inform stoplight that they are to be notified when a SignalChanged event occurs by registering their ObeyTheLight() and/or WarnPedestrian() methods with stoplight‘s SignalChanged event.
- Whenever stoplight‘s SignalChanged event is raised (OnSignalChanged() is called), the SignalChanged event is invoked resulting in car and truck‘s ObeyTheLight() and/or WarnPedestrian() methods being called.
using System; namespace CitySim { public enum SignalColour { Green = 0, Amber, Red} /// <summary> /// Custom object to hold data that can be passed from the method that raises /// the event to the method that handles the event. /// <remarks> /// If no custom data is needed, pass the standard "new EventArgs()" when /// raising the event. /// MS convention is that this class be called {eventtype}EventArgs. /// </remarks> /// </summary> public class TrafficLightEventArgs : EventArgs { public string Notice { get; private set; } public TrafficLightEventArgs(string alterationMsg) { Notice = alterationMsg; } } /// <summary> /// Custom delegate that allows us to pass TrafficLightEventArgs as a /// parameter to the handler. /// <remarks> /// If no custom data is needed then just make eArgs of type EventArgs /// MS convention is that this be called {eventtype}EventHandler. /// </remarks> /// </summary> public delegate void TrafficLightEventHandler (object eSender, TrafficLightEventArgs eArgs); /// <summary> /// This is the object that will raise the events. Whenever the signal's /// colour is changed will raise the appropriate event (i.e. call OnChanged). /// The raised event will notify any listening vehicles (by invoking the /// event's delegate - SignalChanged). /// </summary> public class TrafficLight { /// <summary> /// Think of this as a list of listening methods. Each object that wants /// to be notified of the event adds a method to the list. When /// SignalChanged is invoked, each registered method is called. /// MS convention is that this class be called {eventname}. /// </summary> public event TrafficLightEventHandler SignalChanged; public TrafficLight() { Signal = SignalColour.Red; // whenever we change the light's colour we should raise the // event (call OnChanged). OnSignalChanged( new TrafficLightEventArgs("initialized to " + Signal.ToString())); } public SignalColour Signal { get; private set; } public void CycleColour() { Signal = (SignalColour)(((int)Signal + 1) % 3); // whenever we change the light's colour we should raise the // event (call OnChanged). OnSignalChanged( new TrafficLightEventArgs("cycled to " + Signal.ToString())); } /// <summary> /// Raises the event. If there are listeners registered, the event /// is invoked. /// MS convention is that this method is named On{eventname}. /// </summary> private void OnSignalChanged(TrafficLightEventArgs e) { if (SignalChanged == null) { Console.WriteLine( "\nThere is no one to be notified that the light is " + e.Notice); } else { Console.WriteLine( "\nNotifying subscribers that the light is " + e.Notice); SignalChanged(this, e); } } } /// <summary> /// This is the class that listens for and handles the event. /// </summary> public class Vehicle { public Vehicle(string name) { Id = name; } public string Id { get; private set; } /// <summary> /// This is an example of a method set up to handle (consume) the event. /// </summary> public void ObeyTheLight(object sender, TrafficLightEventArgs e) { string action = Id + " is "; switch (((TrafficLight)sender).Signal) { case SignalColour.Amber: action += "slowing down"; break; case SignalColour.Green: action += "hitting the gas"; break; case SignalColour.Red: action += "stopping"; break; } Console.WriteLine(action); } public void WarnPedestrians(object sender, TrafficLightEventArgs e) { Console.WriteLine(Id + " is blowing its horn"); } } public class Program { static void Main(string[] args) { // Let's create a couple of test vehicles. var car = new Vehicle("Mustang"); var truck = new Vehicle("Peterbilt"); // Now create a traffic light to control them - however, // no one is listening to the traffic light yet. var stoplight = new TrafficLight(); // the traffic light will be Red stoplight.CycleColour(); // Changes the light to Green // Get the car to listen to traffic light by registering the car's // event handler for signal changes. stoplight.SignalChanged += new TrafficLightEventHandler(car.ObeyTheLight); // Now the car is listening to the light - but the truck // is not. stoplight.CycleColour(); // Change the light to Amber stoplight.CycleColour(); // Then to Red // Get the truck listening too. stoplight.SignalChanged += new TrafficLightEventHandler(truck.ObeyTheLight); stoplight.SignalChanged += new TrafficLightEventHandler(truck.WarnPedestrians); stoplight.CycleColour(); // Change the light to Green stoplight.CycleColour(); // Change it to Amber } } }
The output from this is:
There is no one to be notified that the light is initialized to Red There is no one to be notified that the light is cycled to Green Notifying subscribers that the light is cycled to Amber Mustang is slowing down Notifying subscribers that the light is cycled to Red Mustang is stopping Notifying subscribers that the light is cycled to Green Mustang is hitting the gas Peterbilt is hitting the gas Peterbilt is blowing its horn Notifying subscribers that the light is cycled to Amber Mustang is slowing down Peterbilt is slowing down Peterbilt is blowing its horn
I hope that helps you understand events (if you were struggling). But most of all, I hope it helps me.