Professional Documents
Culture Documents
Delegate and Event
Delegate and Event
Delegate and Event
All of us have been exposed to event driven programming of some sort or the
other. C# adds on value to the often mentioned world of event driven programming
by adding support through events and delegates. The emphasis of this article
would be to identify what exactly happens when you add an event handler to your
common UI controls. A simple simulation of what could possibly be going on behind
the scenes when the AddOnClick or any similar event is added to the Button class
will be explained. This will help you understand better the nature of event handling
using multi cast delegates.
Delegates
A delegate in C# is similar to a function pointer in C or C++. Using a delegate allows
the programmer 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, without having to know at compile time which method will be invoked.
In most cases, when we call a function, we specify the function to be called directly.
If the class MyClass has a function named Process, we'd normally call it like this
(SimpleSample.cs):
using System;
namespace Akadia.NoDelegate
{
public class MyClass
{
public void Process()
{
Console.WriteLine("Process() begin");
Console.WriteLine("Process() end");
}
}
That works well in most situations. Sometimes, however, we don't want to call a
function directly - we'd like to be able to pass it to somebody else so that they can
call it. This is especially useful in an event-driven system such as a graphical user
interface, when I want some code to be executed when the user clicks on a button,
or when I want to log some information but can't specify how it is logged.
1
The very basic Delegate
An interesting and useful property of a delegate is that it does not know or care
about the class of the object that it references. Any object will do; all that matters is
that the method's argument types and return type match the delegate's. This makes
delegates perfectly suited for "anonymous" invocation.
where:
Examples:
A delegate will allow us to specify what the function we'll be calling looks
like without having to specify which function to call. The declaration for a
delegate looks just like the declaration for a function, except that in this case, we're
declaring the signature of functions that this delegate can reference.
o Declaration
o Instantiation
o Invocation
using System;
namespace Akadia.BasicDelegate
{
// Declaration
2
public delegate void SimpleDelegate();
class TestDelegate
{
public static void MyFunc()
{
Console.WriteLine("I was called by delegate ...");
}
// Invocation
simpleDelegate();
}
}
}
Compile an test:
# csc SimpleDelegate1.cs
# SimpleDelegate1.exe
I was called by delegate ...
using System;
namespace Akadia.SimpleDelegate
{
// Delegate Specification
public class MyClass
{
// Declare a delegate that takes a single string parameter
// and has no return type.
public delegate void LogHandler(string message);
if (logHandler != null)
3
{
logHandler ("Process() end");
}
}
}
Compile an test:
# csc SimpleDelegate2.cs
# SimpleDelegate2.exe
Process() begin
Process() end
In the simple example above, the Logger( ) function merely writes the string out. A
different function might want to log the information to a file, but to do this, the
function needs to know what file to write the information to (SimpleDelegate3.cs)
4
using System;
using System.IO;
namespace Akadia.SimpleDelegate
{ // Delegate Specification
public class MyClass
{
// Declare a delegate that takes a single string parameter
// and has no return type.
public delegate void LogHandler(string message);
// The use of the delegate is just like calling a function
directly,
// though we need to add a check to see if the delegate is null
// (that is, not pointing to a function) before calling the
function.
public void Process(LogHandler logHandler)
{
if (logHandler != null)
{
logHandler("Process() begin");
}
if (logHandler != null)
{
logHandler ("Process() end");
}
}
}
// The FileLogger class merely encapsulates the file I/O
public class FileLogger
{
FileStream fileStream;
StreamWriter streamWriter;
// Constructor
public FileLogger(string filename)
{
fileStream = new FileStream(filename, FileMode.Create);
streamWriter = new StreamWriter(fileStream);
}
// Member Function which is used in the Delegate
public void Logger(string s)
{
streamWriter.WriteLine(s);
}
5
// Main() is modified so that the delegate points to the Logger()
// function on the fl instance of a FileLogger. When this delegate
// is invoked from Process(), the member function is called and
// the string is logged to the appropriate file.
public class TestApplication
{
static void Main(string[] args)
{
FileLogger fl = new FileLogger("process.log");
The cool part here is that we didn't have to change the Process() function; the code
to all the delegate is the same regardless of whether it refers to a static or member
function.
Compile an test:
# csc SimpleDelegate3.cs
# SimpleDelegate3.exe
# cat process.log
Process() begin
Process() end
Multicasting
Being able to point to member functions is nice, but there are more tricks you can do
with delegates. In C#, delegates are multicast, which means that they can point to
more than one function at a time (that is, they're based off the
System.MulticastDelegate type). A multicast delegate maintains a list of functions
that will all be called when the delegate is invoked. We can add back in the logging
function from the first example, and call both delegates. Here's what the code looks
like:
using System;
using System.IO;
namespace Akadia.SimpleDelegate
{
// Delegate Specification
6
public class MyClass
{
// Declare a delegate that takes a single string parameter
// and has no return type.
public delegate void LogHandler(string message);
if (logHandler != null)
{
logHandler ("Process() end");
}
}
}
// Constructor
public FileLogger(string filename)
{
fileStream = new FileStream(filename, FileMode.Create);
streamWriter = new StreamWriter(fileStream);
}
7
Console.WriteLine(s);
}
myClass.Process(myLogger);
fl.Close();
}
}
}
Compile an test:
# csc SimpleDelegate4.cs
# SimpleDelegate4.exe
Process() begin
Process() end
# cat process.log
Process() begin
Process() end
Events
The Event model in C# finds its roots in the event programming model that is
popular in asynchronous programming. The basic foundation behind this
programming model is the idea of "publisher and subscribers." In this model, you
have publishers who will do some logic and publish an "event." Publishers will then
send out their event only to subscribers who have subscribed to receive the specific
event.
In C#, any object can publish a set of events to which other applications can
subscribe. When the publishing class raises an event, all the subscribed applications
are notified. The following figure shows this mechanism.
8
Conventions
o Event Handlers in the .NET Framework return void and take two
parameters.
o The first paramter is the source of the event; that is the
publishing object.
o The second parameter is an object derived from EventArgs.
o Events are properties of the class publishing the event.
o The keyword event controls how the event property is accessed
by the subscribing classes.
9
Simple Event
Let's modify our logging example from above to use an event rather than a delegate:
using System;
using System.IO;
namespace Akadia.SimpleEvent
{
/* ========= Publisher of the Event ============== */
public class MyClass
{
// Define a delegate named LogHandler, which will encapsulate
// any method that takes a string as the parameter and returns
no value
public delegate void LogHandler(string message);
// Constructor
public FileLogger(string filename)
{
fileStream = new FileStream(filename, FileMode.Create);
streamWriter = new StreamWriter(fileStream);
}
10
}
fl.Close();
}
}
}
Compile an test:
# csc SimpleEvent.cs
# SimpleEvent.exe
Process() begin
Process() end
# cat process.log
Process() begin
Process() end
Suppose you want to create a Clock class that uses events to notify potential
subscribers whenever the local time changes value by one second. Here is the
complete, documented example:
using System;
using System.Threading;
11
namespace SecondChangeEvent
{
/* ======================= Event Publisher
=============================== */
12
{
// Create the TimeInfoEventArgs object
// to pass to the subscribers
TimeInfoEventArgs timeInformation =
new TimeInfoEventArgs(
dt.Hour,dt.Minute,dt.Second);
}
}
}
13
{
Console.WriteLine("Current Time: {0}:{1}:{2}",
ti.hour.ToString(),
ti.minute.ToString(),
ti.second.ToString());
}
}
14
Conclusion
The Clock class from the last sample could simply print the time rather tahn raising
an event, so why bother with the introduction of using delegates? The
advantage of the publisg / subscribe idiom is that any number of classes can be
notified when an event is raised. The subscribing classes do not need to know
how the Clock works, and the Clock does not need to know what they are going to
do in response to the event. Similarly a button can publish an Onclick event, and any
number of unrelated objects can subscribe to that event, receiving notification when
the button is clicked.
The publisher and the subscribers are decoupled by the delegate. This is
highly desirable as it makes for more flexible and robust code. The clock can chnage
how it detects time without breaking any of the subscribing classes. The subscribing
classes can change how they respond to time changes without breaking the Clock.
The two classes spin indepentdently of one another, which makes for code that is
easier to maintain.
In another common scenario, the callback is first registered and later called
asynchronously.
Usually, the higher-level code starts by calling a function within the lower-
level code passing to it a pointer or handle to another function. While the
lower-level function executes, it may call the passed-in function any number
of times to perform some subtask. In another scenario, the lower-level
function registers the passed-in function as a handler that is to be called
asynchronously by the lower-level at a later time in reaction to something.
programming
15
16
Section I.2 Contents
[hide]
• 1 Motivation
• 2 Example
• 3 Implementation
• 4 Special cases
• 5 See also
• 6 External links
Motivation
Example
The following code in C demonstrates the use of callbacks for the specific case of
searching an array for an item (in this case, the first integer greater than 5). First,
the iteration approach:
17
int i;
for (i = 0; i < length; i++) {
if (array[i] > 5) {
break;
}
}
if (i < length) {
printf("Item %d\n", i);
} else {
printf("Not found\n");
}
/* LIBRARY CODE */
int traverseWith(int array[], size_t length,
int (*callback)(int index, int item, void *param),
void *param)
{
int exitCode = 0;
for (int i = 0; i < length; i++) {
exitCode = callback(i, array[i], param);
if (exitCode != 0) {
break;
}
}
return exitCode;
}
/* APPLICATION CODE */
int search (int index, int item, void *param)
{
if (item > 5) {
*(int *)param = index;
return 1;
} else {
return 0;
}
}
Note that traverseWith receives an extra parameter that the callback can use for its
own purposes. Normally a callback uses such a parameter as a pointer to application
data outside its own scope (in this case, the variable that receives the index). This
feature is necessary only in a statically scoped language like C or C++ (in C++ and
18
OO languages other solutions are however possible, see below). Lexically scoped
languages supporting closures (including functional programming languages) can
provide access to application data automatically by callbacks referring to local
variables. In Lisp, for example, the same program would look like this:
; LIBRARY CODE
(defun traverseWith (array callback)
(let ((exitCode nil)
(i 0))
(while (and (not exitCode) (< i (length array)))
(setf exitCode (funcall callback i (aref array i)))
(setf i (+ i 1)))
exitCode))
; APPLICATION CODE
(let (index found)
(setf found (traverseWith array (lambda (idx item)
(if (<= item 5)
nil
(progn
(setf index idx)
t))))))
The callback function is now defined at the point of use, and it refers to "index" by
name. Synchronization concerns have been omitted from these examples, but they
can easily be addressed in the traverseWith function. More importantly, they can be
addressed, or ignored, by changing only that function.
19
20
Implementation
The form of a callback varies among programming languages.
Special cases
Callback functions are also frequently used as a means to handle exceptions arising
within the low level function, as a way to enable side-effects in response to some
condition, or as a way to gather operational statistics in the course of a larger
computation. Interrupt handlers in an operating system respond to hardware
conditions, signal handlers of a process are triggered by the operating system, and
event handlers process the asynchronous input a program receives.
A pure callback function is one which is purely functional (always returns the same
value given the same inputs) and free of observable side-effects. Some uses of
callbacks, such as the sorting example, require pure callback functions to operate
correctly.
21
are registered early, to be invoked at callee's discretion (i.e. when a given event
occurs). Some programming languages also have direct support for this construct
(for instance .NET delegates or Qt's signals and slots).
22