Events
When working with an IDE like Visual Studio, most events, particularly those involving Windows Forms applications, are nonlinear. That is, you might have to wait for a user to click a button or press a key to then respond to that event. In server applications, you might have to wait (and listen) for an incoming network request. These capabilities are provided by events in the .NET Framework. Any developer will notice the connection between events and delegates in technical documentation. This article will assume a working knowledge of the .NET Framework 2.0 and Visual Studio 2005 (2008), with the intention of defining both events and delegates. While these subjects are treated separately, they are interrelated and can be welded together to gain a sharper understanding of the overall concept of having a method invoked upon an event or a reference to that method.
This first section will focus on events, what they are, and what they are capable of when defined in a class. The following section will then focus on delegates, what they are, how they are declared, instantiated, and used. Again, the reader should note that the underlying concept is the invocation of a method to perform an action based on an event, and a method that is invoked by a reference to that method other than a function pointer.
One kind of member that a class can define is an event. A type that defines an event member allows that type (or instances of that type (class)) to notify other objects that something special has occurred. For example, the Button
class offers an event called Click
. When a Button
object is clicked, one or more objects in an application may want to receive notification about this event in order to perform some action. Events are types that allow this interaction. Specifically, defining an event member means that the class is offering the following capabilities:
- A type’s static method or an object’s instance method can register its interest in the type’s event.
- A type’s static method or an object’s instance method can unregister its interest in the type’s event.
- Registered methods will be notified when the event occurs.
Types can offer this functionality when defining an event because they maintain a list of the registered methods. When an event occurs, the type notifies all of the registered methods in the list. The CLR’s event model is based on delegates. A delegate is a type-safe way to invoke a callback’s methods. Callback methods are the means by which objects receive the notifications they subscribe to.
An event is a message sent by an object to signal the occurrence of an action. The action could be caused by user interaction, such as a mouse click, or it could be triggered by some other program logic. The object that raises the event is called the event sender. The object that captures and responds to the event is called the event receiver. In event communication, the event sender class does not know which object or method will receive (or handle) the event it raises. What is needed is an intermediary (or pointer-like mechanism) between the source and the receiver. In other words, the object that receives the event cannot validate the source of the event. The .NET Framework defines a special type (Delegate
) that provides the functionality of a function pointer. Here is code to exemplify an event and introduce delegates. It is not expected for the reader to first understand how the code works, as it will be explained through different sections of this article.
using System;
using System.ComponentModel;
namespace EventSample
{
using System;
using System.ComponentModel;
public class AlarmEventArgs : EventArgs
{
private readonly bool snoozePressed ;
private readonly int nrings;
public AlarmEventArgs(bool snoozePressed, int nrings)
{
this.snoozePressed = snoozePressed;
this.nrings = nrings;
}
public int NumRings
{
get { return nrings;}
}
public bool SnoozePressed
{
get {return snoozePressed;}
}
public string AlarmText
{
get
{
if (snoozePressed)
{
return ("Wake Up!!! Snooze time is over.");
}
else
{
return ("Wake Up!");
}
}
}
}
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
public class AlarmClock
{
private bool snoozePressed = false;
private int nrings = 0;
private bool stop = false;
public bool Stop
{
get {return stop;}
set {stop = value;}
}
public bool SnoozePressed
{
get {return snoozePressed;}
set {snoozePressed = value;}
}
public event AlarmEventHandler Alarm;
protected virtual void OnAlarm(AlarmEventArgs e)
{
if (Alarm != null)
{
Alarm(this, e);
}
}
public void Start()
{
for (;;)
{
nrings++;
if (stop)
{
break;
}
else if (snoozePressed)
{
System.Threading.Thread.Sleep(1000);
{
AlarmEventArgs e = new AlarmEventArgs(snoozePressed,
nrings);
OnAlarm(e);
}
}
else
{
System.Threading.Thread.Sleep(300);
AlarmEventArgs e = new AlarmEventArgs(snoozePressed,
nrings);
OnAlarm(e);
}
}
}
}
public class WakeMeUp
{
public void AlarmRang(object sender, AlarmEventArgs e)
{
Console.WriteLine(e.AlarmText +"\n");
if (!(e.SnoozePressed))
{
if (e.NumRings % 10 == 0)
{
Console.WriteLine(" Let alarm ring? Enter Y");
Console.WriteLine(" Press Snooze? Enter N");
Console.WriteLine(" Stop Alarm? Enter Q");
String input = Console.ReadLine();
if (input.Equals("Y") ||input.Equals("y")) return;
else if (input.Equals("N") || input.Equals("n"))
{
((AlarmClock)sender).SnoozePressed = true;
return;
}
else
{
((AlarmClock)sender).Stop = true;
return;
}
}
}
else
{
Console.WriteLine(" Let alarm ring? Enter Y");
Console.WriteLine(" Stop Alarm? Enter Q");
String input = Console.ReadLine();
if (input.Equals("Y") || input.Equals("y")) return;
else
{
((AlarmClock)sender).Stop = true;
return;
}
}
}
}
public class AlarmDriver
{
public static void Main (string[] args)
{
WakeMeUp w= new WakeMeUp();
AlarmClock clock = new AlarmClock();
clock.Alarm += new AlarmEventHandler(w.AlarmRang);
clock.Start();
}
}
}
We compile this code, eventsample.cs, with a reference to System.dll:
C:\..\v2.0.50727> csc.exe /reference:System.dll eventsample.cs
And now, we run the code:

So, What is a Delegate?
A delegate is a class (that uses the delegate
keyword) that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and can hold references to methods that match its signature. A delegate is thus equivalent to a type-safe function pointer or a callback. While delegates have many other uses, this section will focus on the event-handling functionality of delegates. A delegate declaration is sufficient to define a delegate class. The declaration supplies the signature of the delegate, and the CLR provides the implementation. The following example shows an event delegate declaration:
public delegate void AlarmEventHandler(object sender, EventArgs e);
The standard signature of an event handler delegate defines a method that does not return a value, whose first parameter is of type Object
and refers to the instance that raises the event, and whose second parameter is derived from type EventArgs
and holds the event data. So, a delegate in C# is similar to a function pointer in C or C++. Using a delegate allows us to encapsulate a reference to a method inside a delegate object. The delegate object can then be passed to code, which can call the referenced method (provided the parameters match), without having to know at compile time which method will be invoked. Unlike function pointers in C or C++, delegates are object-oriented, type-safe, and secure. Just as the CLR does not even have to know which .NET language has been compiled, the CLR knows that managed code emits metadata and IL code. The metadata tables are examined to perform type-safe checking to ensure that proper data is being passed to the proper method, as the IL code is JIT's. In short, a delegate declaration defines a type that encapsulates a method with a particular set of arguments and return type. For static methods, a delegate object encapsulates the method to be called. For instance methods, a delegate object encapsulates both an instance and a method on the methods. Recall that the difference between static and instance methods is that one operates on the type itself and the other operates on an instance of the type. If you have a delegate object and an appropriate set of arguments, you can invoke the delegate with the arguments.
Examine the code below:
using System;
namespace Bookstore
{
using System.Collections;
public struct Book
{
public string Title;
public string Author;
public decimal Price;
public bool Paperback;
public Book(string title, string author, decimal price, bool paperBack)
{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
}
}
public delegate void ProcessBookDelegate(Book book);
public class BookDB
{
ArrayList list = new ArrayList();
public void AddBook(string title, string author, decimal price, bool paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}
public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
{
foreach (Book b in list)
{
if (b.Paperback)
processBook(b);
}
}
}
}
namespace BookTestClient
{
using Bookstore;
class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m;
internal void AddBookToTotal(Book book)
{
countBooks += 1;
priceBooks += book.Price;
}
internal decimal AveragePrice()
{
return priceBooks / countBooks;
}
}
class Test
{
static void PrintTitle(Book b)
{
Console.WriteLine(" {0}", b.Title);
}
static void Main()
{
BookDB bookDB = new BookDB();
AddBooks(bookDB);
Console.WriteLine("Paperback Book Titles:");
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
PriceTotaller totaller = new PriceTotaller();
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
totaller.AveragePrice());
}
static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("The C Programming Language",
"Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
bookDB.AddBook("The Unicode Standard 2.0",
"The Unicode Consortium", 39.95m, true);
bookDB.AddBook("The MS-DOS Encyclopedia",
"Ray Duncan", 129.95m, false);
bookDB.AddBook("Dogbert's Clues for the Clueless",
"Scott Adams", 12.00m, true);
}
}
}
Now, examine the output:
Paperback Book Titles:
The C Programming Language
The Unicode Standard 2.0
Dogbert's Clues for the Clueless
Average Paperback Book Price: $23.97
We instantiated the delegate because once the delegate has been declared, a delegate object must be created and associated with a particular method. Like all other objects, a new delegate object is created with a new
expression. Herein lies one principle difference between the object-oriented and the procedural C language: in C, when we declare a variable, we are informing the compiler with the type of data that will function as a variable that will have an assigned value. The compiler then allocates the storage needed for that particular data type. When creating a delegate, however, the argument passed to the new
expression is special — it is written like a method call, but without the arguments to the method, as shown in the following statement:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
When creating a delegate, however, the argument passed to the new expression is special — it is written like a method call, but without the arguments to the method.
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
That statement creates a new delegate object associated with the static method Test.PrintTitle
. The following statement:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
creates a new delegate object associated with the non-static method AddBookToTotal
on the object totaller
. In both cases, this new delegate object is immediately passed to the ProcessPaperbackBooks
method. Note that once a delegate is created, the method it is associated with never changes — delegate objects are immutable.
Responding to an Event
The downloadable file, SystemTimer, is a Windows Forms application that contains a progress bar control. The idea is to control this progress bar control by responding to timer events. Timer objects can be used to throw events after a specified number of milliseconds. You simply create a Windows Forms project, drag and drop a progress bar control onto the form’s surface, and then declare a Timer
object. Download the code into a folder named TimerEvents in the Projects folder of your edition of Visual Studio. Do not try to run the solution file from the Zip archive. When these files are extracted to this new subfolder of the Projects folder, double-click the solution file to see how the progress bar reacts after a certain number of milliseconds pass.
Below is referenced code from Jeffrey Richter’s Book “CLR via C#”. This code is exemplary, and a student of .NET development should refer to this code continually in order to demystify delegates:
using System;
using System.Windows.Forms;
using System.IO;
internal delegate void Feedback(Int32 value);
public sealed class Program {
public static void Main() {
StaticDelegateDemo();
InstanceDelegateDemo();
ChainDelegateDemo1(new Program());
ChainDelegateDemo2(new Program());
}
private static void StaticDelegateDemo() {
Console.WriteLine("----- Static Delegate Demo -----");
Counter(1, 3, null);
Counter(1, 3, new Feedback(Program.FeedbackToConsole));
Counter(1, 3, new Feedback(FeedbackToMsgBox));
Console.WriteLine();
}
private static void InstanceDelegateDemo() {
Console.WriteLine("----- Instance Delegate Demo -----");
Program p = new Program();
Counter(1, 3, new Feedback(p.FeedbackToFile));
Console.WriteLine();
}
private static void ChainDelegateDemo1(Program p) {
Console.WriteLine("----- Chain Delegate Demo 1 -----");
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fb3 = new Feedback(p.FeedbackToFile);
Feedback fbChain = null;
fbChain = (Feedback) Delegate.Combine(fbChain, fb1);
fbChain = (Feedback) Delegate.Combine(fbChain, fb2);
fbChain = (Feedback) Delegate.Combine(fbChain, fb3);
Counter(1, 2, fbChain);
Console.WriteLine();
fbChain = (Feedback) Delegate.Remove(fbChain,
new Feedback(FeedbackToMsgBox));
Counter(1, 2, fbChain);
}
private static void ChainDelegateDemo2(Program p) {
Console.WriteLine("----- Chain Delegate Demo 2 -----");
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fb3 = new Feedback(p.FeedbackToFile);
Feedback fbChain = null;
fbChain += fb1;
fbChain += fb2;
fbChain += fb3;
Counter(1, 2, fbChain);
Console.WriteLine();
fbChain -= new Feedback(FeedbackToMsgBox);
Counter(1, 2, fbChain);
}
private static void Counter(Int32 from, Int32 to, Feedback fb) {
for (Int32 val = from; val <= to; val++) {
if (fb != null)
fb(val);
}
}
private static void FeedbackToConsole(Int32 value) {
Console.WriteLine("Item=" + value);
}
private static void FeedbackToMsgBox(Int32 value) {
MessageBox.Show("Item=" + value);
}
private void FeedbackToFile(Int32 value) {
StreamWriter sw = new StreamWriter("Status", true);
sw.WriteLine("Item=" + value);
sw.Close();
}
}
When you compile and execute this code, notice the command line display lines that correspond with the Windows Forms buttons. Now, recall that a delegate indicates the signature of a callback method. The code starts with the declaration of the internal delegate, Feedback
. Feedback
identifies a method that takes a parameter (an Int32
) and returns a void
. Now, the Program
class defines a private static method named Counter
. This method counts integers from the from
argument to the to
argument. The Counter
also takes an fb
, which is a reference to a Feedback
delegate object. Recall or understand that the CLR requires that any object must be created by calling the new
operator. For instance, a Patient
object would be created like so:
Patient p = new Patient();
The new
operator performs a series of underlying operations, not to mention using the variable (in this case, p
) as reference where the constructor’s parameters are stored. The Counter
loops through all of the integers, and for each integer, if the fb
variable is not null
, the callback method (specified by the fb
variable) is called. This callback method is passed the value of the item being processed, the item number. The callback method can be designed and implemented to process each item in any manner deemed appropriate.