Click here to Skip to main content
Click here to Skip to main content

Using Events and Delegates in C#

, 21 Oct 2005
Rate this:
Please Sign up or sign in to vote.
Understand delegates and encourage support for events in C# classes.

Introduction

There are a number of articles available which deal with delegates. The objective of this article is to present events and delegates in a very straightforward fashion, which will enable and encourage developers to include events in classes that they design and develop, as well as properties (attributes), and methods (operations).

Understanding events

To help understand events, consider the System.Web.UI.WebControls.DropDownList control that is commonly used in ASP.NET web applications. If you drop a DropDownList control called cboDropDownList on an ASP.NET Web Form, then switch to the code view, and type the object name followed by a ., the .NET IDE intellisense will display the available properties, methods, and events.

If our web page wants to subscribe to, and respond to the SelectIndexChanged event, the .NET IDE can be used to select the event, and generate the necessary definitions. Simply press tab to select the event, then type +=, and the IDE will prompt us to press tab to insert an event handler, and tab again to generate a function to receive the event.

The generated code looks like this:

private void Page_Load(object sender, System.EventArgs e)
{
    // add an event handler to deal with 
    // the SelectedIndexChangedEvent
    cboDropDown.SelectedIndexChanged +=
       new EventHandler(cboDropDown_SelectedIndexChanged);
}

private void cboDropDown_SelectedIndexChanged(
                              object sender, EventArgs e)
{

}

When the application runs, if the user selects a new option from the drop down list, the method cboDropDown_SelectedIndexChanged will be called. If the contents of a second drop down list need to change in response to the item currently selected in the first one, then we have the ideal place to insert the code. Using intellisense to explore the exposed interfaces of the various classes that are part of the .NET Framework soon reveals that Microsoft implements events in their objects frequently. However, many developers overlook introducing events into their objects, thereby reducing the usefulness of the classes that they design and build.

How to build events into classes

Let us consider an object that is being constructed using C# to implement the business rules behind creation of invoices:

public class Invoice
{
    private string strInvoiceID;
    private System.DateTime dtmDateRaised;

    public Invoice()
    {
    }

    public string invoiceID 
    {
        get
        {
            return this.strInvoiceID;
        }
        set
        {
            this.strInvoiceID = value;
        }
    }

    public System.DateTime dateRaised
    {
        get
        {
            return this.dtmDateRaised;
        }
        set
        {
            this.dtmDateRaised = value;
        }
    }
}

We are going to suppose that the client application can contain multiple views of the invoice, and that should the invoice be changed in any way, then all of the views would like to be informed such that they can independently update their user interfaces.

The set of steps involved in creating an event to enable this functionality for clients of objects constructed using this class is detailed below:

1. Create a sub-class of the EventArgs class

When we constructed the event handler associated with the DropDownList, it contained two parameters - a sender object, and an EventArgs object:

private void cboDropDown_SelectedIndexChanged(
                            object sender, EventArgs e)
{

}

When constructing our own class that can raise events, we can have whatever parameters we like, but we will opt to stick to the standards that the Microsoft .NET Framework objects adhere to.

The first parameter - object sender – is always the source object that generated the event. In the example above, sender would contain cboDropDown cast into an object (the implementation in the event handler can easily cast it back to a DropDownList).

The second parameter - EventArgs e – contains very little information which is of any use for a DropDownList. The Invoice class is going to pass a more useful set of event arguments as follows:

public class BusinessObjectEventArgs : EventArgs 
{
    private string strPropertyName;
    private object objOldValue;
    private object objNewValue;

    public BusinessObjectEventArgs(string PropertyName, 
                           object OldValue, object NewValue)
    {
        this.strPropertyName = PropertyName;
        this.objOldValue = OldValue;
        this.objNewValue = NewValue;
    }

    public string PropertyName
    {
        get
        {
            return this.strPropertyName;
        }
    }

    public object OldValue
    {
        get
        {
            return this.objOldValue;
        }
    }

    public object NewValue
    {
        get
        {
            return this.objNewValue;
        }
    }
}

Now when an event handler is being called because a property within the object of class Invoice is called, it will include a BusinessObjectEventArgs object. This set of event arguments will be very useful, and will contain more information than can be obtained by having access to the Invoice object itself namely – the name of the property which has changed, its new value, and the value it contained immediately before it changed.

2. Create the delegate

The next step is to create the all-important delegate. Consider the following delegate:

public delegate void BusinessObjectEventHandler(
              object sender, BusinessObjectEventArgs e);

A delegate may be thought of as the blue-print, or interface for a call-back function. When an event in the Invoice class fires, it is going to call an event handler. That event handler is going to be of type BusinessObjectEventHandler i.e. it will contain two parameters, a sender and a BusinessObjectEventArgs object.

Understanding delegates really does come down to that simple definition. A delegate is the interface definition of a call-back function.

3. Create the event

Once an EventArgs sub-class has been designed, and implemented, and the delegate (interface definition for the call-back function) has been declared, the next step is to define an event within the Invoice class as follows:

public event BusinessObjectEventHandler OnChanged;

The Invoice class now contains an event called OnChanged, the event handler for which is of type BusinessObjectEventHandler.

4. Raise the event

When the client application uses the Invoice class, they may subscribe to the OnChanged event, using the approach taken in the Understanding Events section.

The last thing left to do in the Invoice class is to cause the event to be raised to any client application that has subscribed to the event. This is achieved by modifying the two property set functions as follows:

public string invoiceID 
{
    get
    {
        return this.strInvoiceID;
    }
    set
    {
        if (OnChanged != null)
        {
            OnChanged(this, 
              new BusinessObjectEventArgs("invoiceID", 
                               this.strInvoiceID, value));
        }
        this.strInvoiceID = value;
    }
}

public System.DateTime dateRaised
{
    get
    {
        return this.dtmDateRaised;
    }
    set
    {
        if (OnChanged != null)
        {
            OnChanged(this, 
               new BusinessObjectEventArgs("dateRaised", 
                               this.dtmDateRaised, value));
        }
        this.dtmDateRaised = value;
    }
}

Instantiating an Invoice object and subscribing to its events

With the changes to the Invoice class complete to support events, the Invoice class may be used, and the events subscribed to and consumed by a client application as follows:

using BusinessObjects;
using System.Diagnostics;
...

private void AnInvoiceClient()
{
    BusinessObjects.Invoice i = new Invoice();
    i.invoiceID = "1";
    i.OnChanged += 
        new BusinessObjectEventHandler(i_OnChanged1);
    i.OnChanged += 
        new BusinessObjectEventHandler(i_OnChanged2);
    i.dateRaised = System.DateTime.Now;
}

private void i_OnChanged1(object sender, 
                          BusinessObjectEventArgs e)
{
    string strMsg = "object {0} property " + 
                        "{1} changed from {2} to {3}";
    BusinessObjects.Invoice i = (Invoice)sender;
    Debug.WriteLine(String.Format(strMsg, 
                          i.invoiceID, e.PropertyName, 
                          e.OldValue, e.NewValue));
}

private void i_OnChanged2(object sender, 
                         BusinessObjectEventArgs e)
{
    string strMsg = "object {0} property " + 
                       "{1} changed from {2} to {3}";
    BusinessObjects.Invoice i = (Invoice)sender;
    Debug.WriteLine(String.Format(strMsg, 
                         i.invoiceID, e.PropertyName, 
                         e.OldValue, e.NewValue));
}

When AnInvoiceClient is called, the output produced is as follows:

object 1 property dateRaised changed from 
        01/01/0001 00:00:00 to 21/10/2005 23:18:35
object 1 property dateRaised changed from 
        01/01/0001 00:00:00 to 21/10/2005 23:18:35

Note that the client subscribes to the event twice. When the dateRaised property is changed, both i_OnChanged1 and I_OnChanged2 are called by the Invoice class.

The complete code for the finished Invoice.cs implementation of the Invoice class is given below:

using System;

namespace BusinessObjects
{
    public class Invoice
    {
        private string strInvoiceID;
        private System.DateTime dtmDateRaised;

        public event BusinessObjectEventHandler OnChanged;

        public Invoice()
        {
        }

        public string invoiceID 
        {
            get
            {
                return this.strInvoiceID;
            }
            set
            {
                if (OnChanged != null)
                {
                    OnChanged(this, 
                      new BusinessObjectEventArgs("invoiceID", 
                                    this.strInvoiceID, value));
                }
                this.strInvoiceID = value;
            }
        }

        public System.DateTime dateRaised
        {
            get
            {
                return this.dtmDateRaised;
            }
            set
            {
                if (OnChanged != null)
                {
                    OnChanged(this, 
                      new BusinessObjectEventArgs("dateRaised", 
                                    this.dtmDateRaised, value));
                }
                this.dtmDateRaised = value;
            }
        }

    }

    public class BusinessObjectEventArgs : EventArgs 
    {
        private string strPropertyName;
        private object objOldValue;
        private object objNewValue;

        public BusinessObjectEventArgs(string PropertyName, 
                              object OldValue, object NewValue)
        {
            this.strPropertyName = PropertyName;
            this.objOldValue = OldValue;
            this.objNewValue = NewValue;
        }

        public string PropertyName
        {
            get
            {
                return this.strPropertyName;
            }
        }

        public object OldValue
        {
            get
            {
                return this.objOldValue;
            }
        }

        public object NewValue
        {
            get
            {
                return this.objNewValue;
            }
        }
    }

    public delegate void BusinessObjectEventHandler(
              object sender, BusinessObjectEventArgs e);
}

Conclusion

The original premise was that an application envisaged having multiple views of the same Invoice business object. When any one of the views updated that business object, all the other views need to be notified such that they can update their user interface (this is a common application problem, think Windows Explorer, or the Visual SourceSafe user interface etc.).

With the following four simple steps:

  1. Create a sub-class of the EventArgs class.
  2. Create the delegate (interface definition for a call-back function).
  3. Create the event.
  4. Raise the event.

The Invoice class has been enhanced with events to meet the original requirement.

A footnote on designing for events

Events should be an intrinsic part of any class design. An object oriented designer / developer will automatically ask "What properties should my class expose?" and "What methods should my class support?". They will seldom think, "What events should my class raise?" Adding support for events in C# classes is very straightforward, and enhances those classes by adding powerful functionality that cannot be added in any other way. Even if there is no immediate need for events within a class, designers and developers should think about it in future. Often, objects go into production, and end up being consumed by client applications unforeseen in the original design. Even if there is no immediate need for events, if they exist, they will almost certainly be utilized at some time or the other, as the class is instantiated, and used. Design useful events into classes today and avoid having to re-code those classes tomorrow. However, not only are delegates (and therefore events) often over-looked in C# development, but the UML 2.0 standard for class diagrams does not include anywhere to design events into a class (at least not that I have found). If there is no way for a class designer to indicate that they would like events to be built into a class, how are they ever going to get developed?

My recommendation is to use the «event» stereotype when developing UML class diagrams to indicate the design for the event as follows:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Graham Harrison
Web Developer
South Africa South Africa
Graham works as a senior developer / software architect for WebSoft Consultants Limited, devloping using a number of software technologies, concentrating on the C# / .NET technology set.
 
Areas of expertise include document management, n-tier enterprise business systems, and exploitation of workflow technology, as well as a wide range of development methodologies including agile model driven development, and UML.
 

Comments and Discussions

 
Generalits grate dude Pinmembernavi999927-Mar-08 3:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411023.1 | Last Updated 21 Oct 2005
Article Copyright 2005 by Graham Harrison
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid