Introduction

If you just want to see the code involved, go to the end of this article.

I am really writing this for myself because on the rare occasion that I have to rely on events for something, I am constantly forgetting how to write my own events. I don’t write my own events that often because the type of work that I do just doesn’t call for it most of the time. Events are great, but just like any other tool, I use them when I need them. My most recent scenario was a very complicated 3rd party ascx control was doing all kinds of crazy stuff after an aspx page has loaded. I needed the end result produced by the control to drive something else on the page. This was the effort to produce an optimization because the same end result was being generated twice which is just plain wasteful and goes against DRY which is ALWAYS a NO-NO! Therefore created an event to be fired off after the 3rd party control was finished doing all of the crazy stuff it was doing. I used the provided result to drive the aforementioned other stuff on the page. It worked great.

High-level view

When you need to create a new event, it is usually on the spot and in a rush – especially when you haven’t written one in a while. Therefore here are some simple things to remember when encountering this situation:
  1. Your parent object needs a reference to its child object that will be raising the event. Without this, you cannot magically catch the event – aside from bubble events, but let’s not get into that. Bubble events are something you deal with specifically in ASP.NET with pages being the parent and user controls being the child. However, I want to focus on creating custom events.
  2. After your parent object has a reference to its child object, you need to explicitly state that you are listening for an event to be raised from the child object by registering a method handle, located in the parent object, with an event handler delegate provided by the child object reference.
  3. The previous step leads into this step – you need to define your method.

Very simple relatable example

A mother has a baby. The mother is the parent object and the baby is the child object. The mother is explicitly listening to the baby always. The baby provides a crying event that it can raise for any reason. The mother is registered with this event and knows how to handle it when raised. If the mother hears someone else’s baby crying event, they probably won’t be as inclined to go check it out because it isn’t their kid.

Writing your custom event

In C# there are three components that you need to provide in order to get started with events. These components must all reside inside of the child object (which is relative, but for argument’s sake, let’s say it is a child object).
  1. Delegate
  2. Event
  3. Boilerplate raise method (optional, but nice to have)
  4. Optional event arguments class if required, to be declared outside of the child object for the sake of reusability

1. Delegate

public delegate void CryHandler(object sender, CryEventArgs e);
The best way to understand what this is and why you need it is to first understand what a delegate is and how you would use it. Delegates are best thought of as something that executes when the time is right.
Example:
Imagine that you are calling customer service for a company that has notoriously long hold times. Recently, some companies that have been implementing a callback service for when it is your turn to finally speak to someone instead of just waiting for an indeterminate amount of time. You provide a phone number to the callback service so that when it is your turn, you get a callback. In this example you are the parent object, your phone number is the delegate, the callback service is the event that is raised when it is your turn to be helped and the customer service department is the child object. You are listening for a phone call and the customer service callback service needs to call you. It finds you again by using the phone number you provided.

2. Event

public event CryHandler Cry;
You can’t have an event without a delegate. Your event, “Cry”, is what is exposed in order for the parent who is listening for the raised event to register it’s method handle for the delegate to call when the time is right.

3. Raising your event

private void RaiseCryEvent(CryEventArgs e)
{
    if(Cry != null)
        Cry(this, e);
}
This is the code I use for raising events. This is not a hard and fast rule to have this method because, just like everything in programming, it depends on what you are doing. This method implies that you will raise this event from inside of the object that exposes the event to begin with.
One of the most confusing things about this code is, “Why are we checking for null?” Simply put, if a method handle was not provided for the delegate to call back, then the reference will be null. If we do not check for null, then we risk encountering a NullReferenceException.

4. Event arguments class

public class CryEventArgs : EventArgs
{
    //Wouldn't it be nice to know why the baby is crying?
    public string Reason { get; set; }
}
This is an optional step because it depends on what you are doing/designing. The question to ask yourself here is “Do I need to send back data?” 
  • If the answer is no, then you can just use the EventArgs class because you won’t care about what is being sent back. This is okay sometimes if all you are looking for is the callback. Think of a click event on a page, 99.99% of the time, you just care that someone pressed the button, you are not looking at the sender object or the EventArgs e. 
  • If the answer is yes, then you should really create a new Event Arguments class because it just makes life easier. Make things explicit to avoid depending on casting, boxing and unboxing which is what you would need to do if you just use the stock EventArgs class. Don’t do that, it is very bad practice, lazy and pointless.

Using your Custom Event

In order to use your custom event, like I pointed out earlier you need an object reference to your target child object. For the sake of argument, let’s continue with the same mother and baby example.
public class Mother
{
 //Child object reference
 Baby _billy;

 public Mother()
 {
  //Child object
  _billy = new Baby();

  //Registering for a call back or listening for the baby to cry
  _billy.Cry += Billy_CryHandler;

  //Put the baby to sleep, the baby will wake up eventually
  _billy.Sleep();
 }

 //When the baby is awoken, you need to ask the baby 
 //why it woke up and put it back to sleep
 private void Billy_CryHandler(object sender, CryEventArgs e)
 {
  //The reason was passed over using our custom event argument object
  Console.WriteLine("Why are you awake Billy? - " + e.Reason + "n");
  
  Console.WriteLine("Billy, go back to sleep!n");

  //Put the baby back to bed
  _billy.Sleep();
 }
}

public class Baby
{
 //The model method that will be called when the event is raised "Call me back at"
 public delegate void CryHandler(object sender, CryEventArgs e);
 
 //The way that you can register for a call back
 public event CryHandler Cry;

 Random _r;

 public Baby()
 {
  _r = new Random();
 }

 public void Sleep()
 {
  //Get a value between 0 and 10 - which is indeterminate enough for this example
  int sleep = _r.Next(10);

  //Announce that the baby will be put back to bed
  Console.WriteLine("Baby will be sleeping for " + sleep + " seconds.n");

  //Long running process
  System.Threading.Thread.Sleep(sleep * 1000);

  //Callback when the baby wakes up
  RaiseCryEvent(new CryEventArgs() { Reason = "I pissed myself again..." });
 }

 private void RaiseCryEvent(CryEventArgs e)
 {
  //This is syntactic sugar - a one liner that replaces 
  //the if statement in the prior example
  Cry?.Invoke(this, e);
 }
}

Leave a Reply

Your email address will not be published. Required fields are marked *