Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / .NET

How To: Using Events and Understanding Their Inheritance from the Delegate Type

0.00/5 (No votes)
17 Jul 2015CPOL3 min read 4.5K  
How To: Using Events and Understanding Their Inheritance from the Delegate Type

The .NET Framework provides a straight-forward and fairly easy syntax to use for callbacks. These callbacks can be used for all sorts of messaging within your system. These messages can signal a UI event, like a button press, or they can be used to send a message to many objects serially (multicasting).

The basis for all of this communication comes from ye olden callback. The callback is simply a method where a pointer to a function is passed and then that pointer is used to (you guessed it) call back to that function. This works well and has for years. However, the problem with this methodology is that it is not type-safe. That is, the function callback is merely a pointer to a location in memory where the function resides. There is no guarantee that the function will have the expected signature of the code doing the callback. This is where delegates come into play in the .NET Framework.

System.Delegate is a type in the .NET Framework Class Library that is used as a type-safe wrapper for a callback. The delegate allows the CLR to ensure that all calls through the delegate will have the proper parameters. Further, System.Delegate is extended with the System.MulticastDelegate type to allow for callbacks to multiple clients.

Events are a special type of delegate that provide protection for event calling and for the underlying delegates invocation list. That is, only the class that defined the class may invoke the event and subscribers are allowed to add/remove only themselves from the invocation list. Events are commonly used with UI objects, such as buttons, grids, trees, and other controls to callback to the underlying code when the user performs an action.

Defining and using events in the .NET framework is really straight-forward. This sample shows the 3 steps to create and use a simple event:

C#
using System;
using System.Threading;

namespace SimpleEventsExample
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // This is a simple example to demonstrate events. 
    // Remember these 3 things when setting up an event:
    // 1. Create a Type derived from EventArgs to pass back 
    //    information pertaining to the event.
    // 2. Create an event that passes back the 
    //    EventArgs instance in the object that is to publish the event
    // 3. Subscribe to the event in the objects that are to listen to the event.
    ///////////////////////////////////////////////////////////////////////////////////////////////

    /// 1. Create a Type derived from EventArgs to pass back information pertaining to the event.
    ///
    /// This is a Type derived from EventArgs whose payload is the current time at the time of the
    /// event and the seconds that have transpired since the beginning of the run.
    internal class SecondsCountdownClockExpiredArgs : EventArgs
    {
        internal SecondsCountdownClockExpiredArgs(DateTime currentTime, int secondsEllapsedSinceStart)
        {
            CurrentTime = currentTime;
            SecondsEllapsedSinceStart = secondsEllapsedSinceStart;
        }
        internal DateTime CurrentTime { get; set; }
        internal int SecondsEllapsedSinceStart { set; get; }
    }

    /// 2. Create an event that passes back the EventArgs instance 
    ///    in the object that is to publish the event
    ///
    /// This is the object that will be firing the event. It will allow subscription to the event via
    /// a public event.
    internal class SecondsCountdownClock
    {
        private const int MillisecondsInASecond = 1000;
        private readonly int _timerInterval;
        private int _secondsEllapsedSinceStart;
        private readonly int _seconds;

        // Define a public event to which listeners can sign up for notification
        public event EventHandler<SecondsCountdownClockExpiredArgs> 
        	SecondsCountdownClockExpired;

        internal SecondsCountdownClock(int seconds)
        {
            _seconds = seconds;
            _timerInterval = seconds * MillisecondsInASecond;
        }

        // Cheesy countdown timer that wakes up and fires based on the interval entered.
        internal void StartTheCountdown()
        {
            Console.WriteLine("Seconds Countdown Clock set to expire every {0} seconds", _seconds);
            for (; ; )
            {
                Thread.Sleep(_timerInterval);
                IncrementTimer();
            }
        }

        // When the interval has been reached, this function is called. 
        // If there are subscribers to the event,
        // create a new event args class, put information in the class, and fire the event.
        private void IncrementTimer()
        {
            _secondsEllapsedSinceStart += _seconds;
            if (SecondsCountdownClockExpired != null) SecondsCountdownClockExpired
            (this, new SecondsCountdownClockExpiredArgs(DateTime.Now, _secondsEllapsedSinceStart));
        }
    }

    /// 3. Subscribe to the event in the objects that are to listen to the event.
    ///
    /// This observer object will subscribe to the clock event 
    /// and will output information to the console
    /// about the event.
    internal class ClockObserver
    {
        internal ClockObserver(SecondsCountdownClock secondsCountdownClock)
        {
            // Subscribe to the SecondsCountdownClockExpired event
            secondsCountdownClock.SecondsCountdownClockExpired += 
            		secondsCountdownClock_SecondsCountdownClockExpired;
        }

        static void secondsCountdownClock_SecondsCountdownClockExpired
        		(object sender, SecondsCountdownClockExpiredArgs e)
        {
            Console.WriteLine("Current Time: {0:h:mm:ss} 
                   Seconds Ellapsed Since Start: 
            {1}", e.CurrentTime, e.SecondsEllapsedSinceStart);
        }
    }

    /// This program is a test harness that creates the 
    /// countdown clock and creates and observer to listen to clock events.
    /// After it generates the clock and observer, it kicks things off by starting the clock.
    class Program
    {
        static void Main(string[] args)
        {
            // Create the clock that will publish clock events
            var seconds = Convert.ToInt16(args[0]);
            var theSecondsCountdownClock = new SecondsCountdownClock(seconds);

            // Create the observer that will listen to clock events 
            // and pass the clock pointer to its constructor
            new ClockObserver(theSecondsCountdownClock);

            // Kick off the clock
            theSecondsCountdownClock.StartTheCountdown();
        }
    }
}

Here is the output:

Output from the Seconds Countdown Clock - Demonstrating Events

Output from the Seconds Countdown Clock – Demonstrating Events

Although this sample isn’t very complicated, it is interesting to see what is going on under the covers with the Event type. The Event type is really a special type of Delegate, which can be seen when examining the intermediate language using the ILDASM.exe tool. A cursory look at the SimpleEventsExample.SecondsCountdownClock type shows the add and remove methods for the event:

ILdasm With Event Add and Remove

ILdasm With Event Add and Remove

Now look at the deconstruction of the add_SecondsCountdownClockExpired into IL:

Intermediate Language Showing Delete Combine

Intermediate Language Showing Delete Combine

You can see from the ILDASM output on line IL_000b that System.Delegate::Combine is the underlying mechanism used for adding to the event listener. Further, looking at the IL for remove shows the underlying delegate remove.

Could you use a Delegate type for this?  Yes, you could, but the Event type is particularly designed for this sort of work. It can only be raised in the class in which it was defined and it protects the invocation list (the list in the underlying delegate class) from being modified arbitrarily, which protects your code and makes for more robust software.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)