guid|web
Using Delegates and Events
If you are familiar with Windows programming, you’ve most likely dealt with
callbacks. Callbacks are method calls that are executed when some event happens
http://www.syngress.com.70 Chapter 2 • Introducing C# Programming
during processing. For instance, a callback can be established to handle the pro-cessing
of an incoming message on a communications port. Another part of the
communications program can wait for messages on a communications port and
invoke the callback whenever a new message arrives. Function pointers perform
the same sort of tasks in straight C/C++ programs.
Delegates in C# improve on method callbacks in two areas. Delegates are type
safe, unlike callbacks in Windows programming. In addition, delegates can call
more than one callback when an event occurs. This is termed multicasting.
Delegates
Let’s extend our employees sample to use delegates.This sample simulates a back-ground
process that receives messages to add new employees to the employee list.
Our queue will be a static array, but in the real world it could be a message
queue (Microsoft Message Queue [MSMQ]), a socket, or some other type of
queue. The source code in Figure 2.7 shows the relevant portions of the sample
pertaining to delegates.The full source code for this sample is on the CD in the
file Delegates.cs.
Figure 2.7 Relevant Portions of the Delegates.cs Program Listing
using System;
using System.Collections;
/// <summary>
/// Contains the program entry point for the Delegates Sample.
/// </summary>
class DelegatesSample
{
static void Main( string[] args )
{
try
{
// Create a container to hold employees
Employees employees = new Employees(4);
// Create and drain our simulated message queue
EmployeeQueueMonitor monitor =
http://www.syngress.com
Continued.Introducing C# Programming • Chapter 2 71
new EmployeeQueueMonitor( employees );
monitor.start();
monitor.stop();
// Display the employee list on screen
Console.WriteLine(
"List of employees added via delegate:" );
foreach ( Employee employee in employees )
{
string name = employee.FirstName + " " +
employee.MiddleName + " " + employee.LastName;
string ssn = employee.SSN;
Console.WriteLine( "Name: {0}, SSN: {1}", name, ssn );
}
}
catch ( Exception exception )
{
// Display any errors on screen
Console.WriteLine( exception.Message );
}
}
}
/// <summary>
/// Simulates our message queue.
/// </summary>
class EmployeeQueueMonitor
{
// Delegate signature
http://www.syngress.com
Figure 2.7 Continued
Continued.72 Chapter 2 • Introducing C# Programming
public delegate void AddEventCallback( string FirstName,
string LastName, string MiddleName, string SSN );
// Instance of the delegate
private AddEventCallback m_addEventCallback;
private Employees m_employees;
private int m_lengthQueue;
private string[, ] m_msgQueue =
{
{"Timothy", "Arthur", "Tucker", "555-55-5555"},
{"Sally", "Bess", "Jones", "666-66-6666" },
{"Jeff", "Michael", "Simms", "777-77-7777"},
{"Janice", "Anne", "Best", "888-88-8888" }
};
public EmployeeQueueMonitor( Employees employees )
{
m_employees = employees;
m_lengthQueue = 4;
// Create an instace of the delegate and register the
// addEmployee method of this class as a callback.
m_addEventCallback = new AddEventCallback(
this.addEmployee );
}
// Drain the queue.
public void start()
{
if ( m_employees == null )
return;
http://www.syngress.com
Figure 2.7 Continued
Continued.Introducing C# Programming • Chapter 2 73
for ( int i = 0; i < m_lengthQueue; i++ )
{
string FirstName = m_msgQueue[i,0];
string MiddleName = m_msgQueue[i,1];
string LastName = m_msgQueue[i,2];
string SSN = m_msgQueue[i,3];
// Invoke the callback registered with the delegate
Console.WriteLine( "Invoking delegate" );
m_addEventCallback( FirstName, LastName, MiddleName,
SSN );
}
}
public void stop()
{
// In a real communications program you would shut down
// gracefully.
}
// Called by the delegate when a message to add an employee
// is read from the message queue.
public void addEmployee( string FirstName, string MiddleName,
string LastName, string SSN )
{
Console.WriteLine( "In delegate, adding employee\r\n" );
int index = m_employees.Length;
m_employees[index] = new Employee ( FirstName, MiddleName,
LastName, SSN );
}
}
http://www.syngress.com
Figure 2.7 Continued.74 Chapter 2 • Introducing C# Programming
Single Cast
The source code in the previous section is an example of a single cast delegate. A
single cast delegate invokes only one callback method. Let’s examine our previous
sample to see this.
The EmployeeQueueMonitor class simulates a message queue. It contains a static
array that holds the current messages. At the top of EmployeeQueueMonitor are the
following lines:
public delegate void AddEventCallback( string FirstName,
string LastName, string MiddleName, string SSN );
private AddEventCallback m_addEventCallback;
The first statement defines a delegate and the parameters an object instance
of the delegate takes. In this case, we callback to a method that takes first name,
last name, middle name, and SSN. We do this whenever a request to add a new
employee appears in the message queue.
The second statement declares a member variable to hold our delegate. It is
initially set to null. A new object instance must be created prior to making
method calls through the delegate. An object instance is instantiated in the con-structor
of EmployeeQueueMonitor.
m_addEventCallback = new AddEventCallback( this.addEmployee );
This statement creates a new object instance of the delegate. The delegate
takes as an argument the method to call when the delegate is invoked. In this
case, whenever the delegate is invoked, the method that will execute is
EmployeeQueueMonitor.addEmployee.
In the start method of EmployeeQueueMonitor is the following code:
for ( int i = 0; i < m_lengthQueue; i++ )
{
string FirstName = m_msgQueue[i,0];
string MiddleName = m_msgQueue[i,1];
string LastName = m_msgQueue[i,2];
string SSN = m_msgQueue[i,3];
// Invoke the callback registered with the delegate
Console.WriteLine( "Invoking delegate" );
http://www.syngress.com.Introducing C# Programming • Chapter 2 75
m_addEventCallback( FirstName, LastName, MiddleName, SSN );
}
This code simulates draining the message queue of any waiting messages.The
callback function is invoked by treating the m_addEventCallback member variable
as if it were a method call passing it our four parameters. Note that you do not
specify the callback itself when making the call.The delegate maintains the
address of the callback internally and therefore knows the method to call.The
following example shows what not to do:
// Incorrect
m_addEventCallback.addEmployee( FirstName, LastName, MiddleName, SSN );
Multicast
The true power of delegates becomes apparent when discussing multicast dele-gates.
Let’s extend our previous example a bit further. Because background pro-cesses
do not usually have a user interface for human interaction, they typically
log incoming events for later review. Let’s add a second callback to our sample to
log incoming add employee requests.The relevant snippets of code are shown in
Figure 2.8.The full source code is for this sample is on the CD in the file
Multicasting.cs.
Figure 2.8 Relevant Portions of the Multicasting.cs Program Listing
class EmployeeQueueMonitor
{
// Delegate signature for add employee event callback
public delegate void AddEventCallback( string FirstName,
string LastName, string MiddleName, string SSN );
// Instance of the delegate
private AddEventCallback m_addEventCallback;
private EmployeeQueueLogger m_logger;
public EmployeeQueueMonitor( Employees employees )
{
http://www.syngress.com
Continued.76 Chapter 2 • Introducing C# Programming
m_employees = employees;
m_lengthQueue = 4;
m_logger = new EmployeeQueueLogger( "log.txt" );
// Register the methods that the delegate will invoke when an
// add employee message is read from the message queue
m_addEventCallback =
new AddEventCallback( this.addEmployee );
m_addEventCallback +=
new AddEventCallback( m_logger.logAddRequest );
}
// Drain the queue.
public void start()
{
if ( m_employees == null )
return;
for ( int i = 0; i < m_lengthQueue; i++ )
{
string FirstName = m_msgQueue[i,0];
string MiddleName = m_msgQueue[i,1];
string LastName = m_msgQueue[i,2];
string SSN = m_msgQueue[i,3];
Console.WriteLine( "Invoking delegate" );
// Invoke the delegate passing the data associated with
// adding a new employee resulting in the subscribed
// callbacks methods being executed, namely
// Employees.this.addEmployee()
http://www.syngress.com
Figure 2.8 Continued
Continued.Introducing C# Programming • Chapter 2 77
// and EmployeeQueueLogger.logAddRequest()
m_addEventCallback( FirstName, LastName, MiddleName,
SSN );
}
}
// Called by delegate whenever a new add employee message
// appears in the message queue. Notice the signature matches
// that requried by AddEventCallback
public void addEmployee( string FirstName, string MiddleName,
string LastName, string SSN )
{
Console.WriteLine( "In delegate, adding employee\r\n" );
int index = m_employees.Length;
m_employees[index] = new Employee ( FirstName, MiddleName,
LastName, SSN );
}
}
/// <summary>
/// Writes add employee events to a log file.
/// </summary>
class EmployeeQueueLogger
{
string m_fileName;
public EmployeeQueueLogger( string fileName )
{
m_fileName = fileName;
}
// Called by delegate whenever a new add employee message
http://www.syngress.com