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:
using System;
using System.Threading;
namespace SimpleEventsExample
{
internal class SecondsCountdownClockExpiredArgs : EventArgs
{
internal SecondsCountdownClockExpiredArgs(DateTime currentTime, int secondsEllapsedSinceStart)
{
CurrentTime = currentTime;
SecondsEllapsedSinceStart = secondsEllapsedSinceStart;
}
internal DateTime CurrentTime { get; set; }
internal int SecondsEllapsedSinceStart { set; get; }
}
internal class SecondsCountdownClock
{
private const int MillisecondsInASecond = 1000;
private readonly int _timerInterval;
private int _secondsEllapsedSinceStart;
private readonly int _seconds;
public event EventHandler<SecondsCountdownClockExpiredArgs>
SecondsCountdownClockExpired;
internal SecondsCountdownClock(int seconds)
{
_seconds = seconds;
_timerInterval = seconds * MillisecondsInASecond;
}
internal void StartTheCountdown()
{
Console.WriteLine("Seconds Countdown Clock set to expire every {0} seconds", _seconds);
for (; ; )
{
Thread.Sleep(_timerInterval);
IncrementTimer();
}
}
private void IncrementTimer()
{
_secondsEllapsedSinceStart += _seconds;
if (SecondsCountdownClockExpired != null) SecondsCountdownClockExpired
(this, new SecondsCountdownClockExpiredArgs(DateTime.Now, _secondsEllapsedSinceStart));
}
}
internal class ClockObserver
{
internal ClockObserver(SecondsCountdownClock secondsCountdownClock)
{
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);
}
}
class Program
{
static void Main(string[] args)
{
var seconds = Convert.ToInt16(args[0]);
var theSecondsCountdownClock = new SecondsCountdownClock(seconds);
new ClockObserver(theSecondsCountdownClock);
theSecondsCountdownClock.StartTheCountdown();
}
}
}
Here is the output:
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
Now look at the deconstruction of the add_SecondsCountdownClockExpired
into IL:
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.
CodeProject