C# - Using Events Correctly
This is more of a rant than a howto, but it will also show "how to" use
events correctly. I have been noticing a lot of bad usage, or exposure, of
events to the public in some code I have been working with.
I am assuming you know the difference, in C#, between a delegate and event- as well as when to
use a delegate and when to use an event. I am not a Microsoft MVP or what ever, but I have
been programming in C# since 2002ish.
Defining an Event
Before you can go create an event, there must be a delegate type for the event you are going to fire. So say we have a really simple Robot class, from a super high-tech class libary that looks like this.
using System;
namespace CrankHouse.Awesome
{
public class Robot
{
private bool isOn;
private int robotId;
private static int ROBOT_COUNT;
public Robot()
{
ROBOT_COUNT++;
robotId = ROBOT_COUNT;
isOn = false;
}
// Turn the robot on.
public void PowerUp()
{
inOn = true;
}
// Turn the robot off.
public void PowerDown()
{
inOn = false;
}
public bool IsOn
{
get { return isOn; }
}
public int RobotID
{
get{ return robotId; }
}
}
}
The Delegate
So let's say that sometime we might want to let other classes know when the state of our robot has changed. For example, other objects might be interested in knowing when a Robot object is turned on or off. First we have to declare the the delegate for that type of action.
namespace CrankHouse.Awesome
{
public delegate void RobotPowerChangedHandler( object sender, System.EventArgs e );
}
The Event
Great, that did nothing for the Robot class yet, because it doesn't define any members of type
"RobotPowerChangeHandler" ( a delegate is basically a type in .NET as is the Robot class ). I am
going to skip a bunch, but I am going to add an event(s) to the Robot class. I want
to give classes to ability to listen in to when the state ( on/off ) of a Robot object changes. To
do that is pretty simple.
We just add this to to Robot class:
public class Robot
{
private event RobotPowerChangedHandler onPowerChanged;
.
.
.
public event RobotPowerChangedHandler OnPowerChanged
{
add
{
onPowerChanged += value;
}
remove
{
onPowerChanged -= value;
}
}
}
Now we have given other classes the ability to listen for changes in the state of the power of an instance of our Robot class. Right now, nothing would happen because the event is never fired by the Robot class. I am going to add the code to fire the event when the Robot is turned on. It's the same for when it's turned off. So I add this to the PowerUp() and PowerDown() methods:
.
public void PowerUp()
{
isOn = true;
if( onPowerChanged != null )
{
EventArgs ea = new EventArgs();
onPowerChanged(this, ea);
}
}
.
Subscribing to an Event
So now let's say we have a RobotManager class that does some awesome managing of instances of the death defying Robot class. The RobotManager would like to track when Robot's are turned on and off. We might have a method of the RobotManager class that returns and fully operational imperial Robot like this, and has it do something wild:
public Robot CreateAwesomebot()
{
Robot robo = new Robot();
// Subscribe to the OnPowerChanged event.
robot.OnPowerChanged += new RobotPowerChangedHandler(LogPowerChanged);
return robot;
}
// Method to listen for when the Robot object has been powered up or down.
private void LogPowerChanged( object sender, System.EventArgs e )
{
Robot r = sender as Robot;
StreamWriter sw = File.AppendText("robot_power.log");
if( r.IsOn )
sw.WriteLine("Robot {0} has been powered up.", r.RobotID);
else
sw.WriteLine("Robot {0} has powered down.", r.RobotID);
sw.Flush();
sw.Close();
}
So now I have subscribed the Robot's OnPowerChanged event in the CreateAwesomebot method.
So when a Robot, created by that method, has been powered up or down the OnPowerChanged event is fired
and the RobotManager's LogPowerChanged method will log it.
If you know how to use delegates you will see that I am doing just that, I created the LogPowerChanged method. The method
I created has the same method signature ( return type and parameter types ), as the RobotPowerChangedHandler
delegate. I then use the event-property to add my event handle to actually subscribe to the event. By using the
"+=" operator and the event/add/remove keywords it is treated a little differently. The event type in C# overloads
the "+=" operator and allows classes to add ( and remove ) event handlers.
Clients Should Not Fire the Events!
It really annoys me when someone doesn't stick to the rules of encapsulation, especially when the language makes it
so easy to stick to.
I have been seeing the following pattern ( a lot ) in the code I am currently working with. The events are declared as
public. So my (WRONG)declaration would look something like this:
public event RobotPowerChangedHandler onPowerChanged;
So now JoeBlow can fire the onPowerChanged event of the Robot regardless of the state. Let's say for a minute that I did declare the event the wrong way. Now instead of subscribing to the event the way it should be done, any instance can fire the onPowerChanged event, even if the power state has not changed. All objects that did subscribe to the event are being lied to.
public class Foo
{
public static void Main(string [] args)
{
RobotManager rm = new RobotManager();
Robot robot = rm.CreateAwesomebot();
robot.onPowerChanged(new Robot(), new System.EventArgs()); // The power state really hasn't changed so the event should not be fired.
robot.onPowerChanged(new Robot(), new System.EventArgs()); // The power state really hasn't changed so the event should not be fired.
robot.onPowerChanged(new Robot(), new System.EventArgs()); // The power state really hasn't changed so the event should not be fired.
robot.PowerUp(); // Now the event should be fired!
}
}
Now the log file of the RobotManager has a 3 messages stating the the Robot's power state has changed,
when it hasn't! It's really no one's fault except the person who wrote the shitty code in the Robot class
allowing client classes to falsely fire the onPowerChanged event.
That, to me, violates encapsulation for two
reasons:
- The client class should not have access to the onPowerChanged event, it should only be fired when the PowerUp() method is called. This can lead to inconsistencies in the expected behavior of the events.
- It shouldn't be the responsibilty of the client class to fire those events. By declaring the event the Robot class is basically saying, "When my power state has changed, I will let you know. You don't tell me". Now, thanks to the Foo class, the RobotManager is getting false information. By looking at the log file, how can we truly know what Robot's are really on or off?
Imagine if on the codebehind of an ASP.NET web form, the Button class exposed the OnClick event the wrong way? I could be clicking buttons all over the place, even if they weren't clicked! Annoying, very annoying.


