Custom Events and Delegates, An Overview


Creating custom events allow us to tailor events to be the most descriptive for the given tasks. It is common practice to also add in additional information to a custom event to provide additional information. In this post I will show you how to create a custom event handler to broadcast Database Errors throughout an application. This example allows you to have data processing methods that go through a large number of operations but internally handle errors, raise the event, and then keep processing.

This example is broken into the following parts:

  • Creating a custom EventArgs class
  • Declaring a delegate to handle your custom event
  • Declaring and raising a public event for consumption
  • Subscribing to the event from another process

Creating a custom EventArgs Class

First we need to create a custom class to handle the information for our event, we will want to have this class derive from the EventArgs class. Below is an example of a DatabaseErrorEventArgs class that I have created, below the code sample is a summary of what is in the code.

public class DatabaseErrorEventArgs :EventArgs
{
    
#region Private Data Members
    System.Data.SqlClient.SqlException mException
;
    string 
mSectionDetail;
    bool 
mIsCritical;
    #endregion

    #region
 Constructor
    
/// 
    /// Class constructor, all parameters are required all the time!
    /// 

    /// 
PARAM name="ex">The SQL Exception raised by the application
    /// 
The section of the process that caused the error
    /// 
An indicator if the error was a critical error
    public DatabaseErrorEventArgs(System.Data.SqlClient.SqlException ex, string section, bool critical)
    {
        
this.mException ex;
        this
.mSectionDetail section;
        this
.mIsCritical critical;
    
}
    
#endregion

    #region
 Public Properties
    
/// 
    /// Returns the passed SQL Exception detailing the error
    /// 

    
public System.Data.SqlClient.SqlException DatabaseError
    {
        
get return mException}
    }
    
/// 
    /// Returns information regarding the section of the application that returned the error
    /// 

    
public string ErrorSection
    {
        
get return mSectionDetail}
    }
    
/// 
    /// Returns an indicator designating if the failure was critical
    /// 

    
public bool IsCritical
    {
        
get return mIsCritical}
    }
    
#endregion
}

If you notice in the constructor I require that all three parameters be sent, I also provided three public properties to expose the values of the internal members. In addition to the methods I have listed here you might also want to override the "ToString" method of the EventArgs class to provide custom text when calling "ToString".

Declaring a delegate to handle your custom event

I think everyone at times has been confused about delegates and what they are, I have found the following "description" very helpful. "A delegate serves as a special event handler, identifying the signature of our event. This ensures that ALL handlers of the event will have all desired event information." With this in mind for our DatabaseErrorEvent we want to ensure that the listener to the event knows the following: Who created the error and what the details were. Therefore we will have 2 parameters in our delegate. (Sender and e to follow the event standards). A delegate is declared as a void method with no body and the delegate keyword, an example of our delegate is below.

public delegate void DatabaseErrorEvent(object sender, DatabaseErrorEventArgs e);

This creates a special event handler so now we can raise events elsewhere in our application and state that the event is a DatabaseErrorEvent. By doing this we will ensure that the DatabaseErrorEventArgs is provided. Since the delegate and the EventArgs class are so closely related I typically include them both in the same .cs file in my solution as it makes it much easier to find the declaration later.

Declaring and raising a public event for consumption

Now that we have an EventArgs class and a delegate it is time to create and raise an event from our code for users to subscribe to. First we need to declare the event, this event declaration must be within a class and should be formed like the following.

public event DatabaseErrorEvent CriticalFailure;

This creates an event called CriticalFailure which is of the type DatabaseErrorEvent, which when raised will provide information regarding the sender and a populated instance of DatabaseErrorEventArgs. To raise this event it is farily simple, you will want to create a protected virtual void method to accomplish this. The reason for using "protected virtual" instead of private or internal is to ensure that if the class was ever extended/inheritied in the future that the new implementation could overrride and call the method.

This method to raise the event is traditionally called an OnError event, therefore for ease of maintaining your code you should name it consistently. In the case of our example the event is CriticialFailure, therefore the method should be OnCriticalFailure. Below you will find the code for the OnCriticalFailure method, I will describe a few things below the code sample.

protected virtual void OnCriticalFailure(DatabaseErrorEventArgs e)
{
    
if (this.CriticalFailure != null)
    {
        
this.CriticalFailure(this, e);
    
}
}

In this statement you will notice that we check to ensure that CriticalError is not equal to null. This simply ensures that we have at least one subscriber to the method. If we have no subscribers it is not necessary to raise the event. In your code when you want to raise a "CriticalError" you simply call OnCriticalError passing it a valid DatabaseErrorEventArgs instance. You can view this code in the linked file below.

Subscribing to the event from another process

Now that you have the EventArgs class created, the deletgate created and all of the methods in place to raise the event your final step is to subscribe and respond to the event from elsewhere in your code. This part is fairly simple. Simply instantiate your worker class, add the handler which points to the method that will respond to the raised event. Below is an example. (Also included in the code sample).

private void btnGo_Click(object sender, EventArgs e)
{
    
//Clear results
    
lblResult.Text "";

    
//instantiate the worker
    
DemonstrationWorker oWorker = new DemonstrationWorker();

    
//Subscribe to the criticial error event
    
oWorker.CriticalFailure += new DatabaseErrorEvent(oWorker_CriticalFailure);

    
//Call it's do work method
    
oWorker.DoWorkAndRaiseEvent();

    
//Mark as done
    
lblResult.Text "Finished";
}

/// 
/// Responds to the raising of the critical failure notice
/// 
/// 
/// 

void oWorker_CriticalFailure(object sender, DatabaseErrorEventArgs e)
{
    
//Show it was done
    
MessageBox.Show("Critical Failure Indicated!! \n" + e.DatabaseError.Message);
}

Note the line that starts with "oWorker.CriticalFailure +=" this is the line that allows your calling class to listen for the CriticalFailureEvent. Notice the handler that we are adding is declared as a new DatabaseErrorEvent which is what our delegate was. The value inside the ()'s is the name of the method to call when the event is raised.

If you run the included sample application when you click on the "Perform Demo" button you should notice a 2 second delay, then you should get a message box, then after about 6 more seconds you will see the text "Finished" in the display label. This was a fairly simple example of how you can create and generate your own events. If there are any questions or if you have ideas for other topics please let me know.

Up Next
    Ebook Download
    View all
    Learn
    View all