(How)
C^# You Are - 4 March 2007
This week's questions are on delegates and events.
- What is a delegate? Answer
A delegate is basically a function type definition. A delegate defines the
signature (including the return type) of a function. All functions that have
the same signature can be used wherever the delegate type is specified. For
example all events have a delegate type. Any function that has a matching
signature can be assigned to the event. Callbacks and event handlers
are the two most common uses for delegates.
public delegate void MyDelegate ( string message );
public class SomeClass
{
public void CallDelegate ( MyDelegate func, string message )
{
func(message);
}
}
Those with C++ backgrounds can relate a delegate to a function pointer. Delegates
allow us to call functions without knowing in advance what specific function(s)
we will call.
- What is an event? Answer
An event is any outside stimuli that might be of interest. In the .NET world
we define events on classes. Interested parties can subscribe to the event
and receive a callback (on a delegate) whenever the event is raised. It is
up to the owning class to actually raise the event. An event always uses a
delegate to define the functions that it can call back.
For example we might create
a worker class that raises an event when the work is done.
public class Worker
{
public event EventHandler WorkComplete;
public void Start ( )
{
...
WorkComplete(this, EventArgs.Empty);
}
}
- What is the standard signature of event handlers? Answer
The first parameter is always the sender as an object. The
second parameter is always an EventArgs-derived class containing
the event arguments. The function returns nothing. The event handler
delegate always ends in -EventHandler and the argument class always
ends in -EventArgs.
public sealed class WorkCompleteEventArgs : EventArgs
{
...
}
public delegate void WorkCompleteEventHandler (object sender, WorkCompleteEventArgs
e );
public class Worker
{
public event WorkCompleteEventHandler WorkComplete;
public void Start ( )
{
...
WorkComplete(this, new WorkCompleteEventArgs(...));
}
}
Callback delegates do not have this requirement. Note that this is strictly
a standard practice. The compiler does not actually enforce any of this.
- On what thread does the event handler get called? Answer
Event handlers are called on the thread that raised the event. In general
this is the primary thread. Events are not some threading technique.
They run synchronously just like any other method. Their benefit lies in calling
functions that were not known at compile time.
- How do you asychronously raise an event? Answer
All delegates have a BeginInvoke method that can be called to asynchronously
raise the event on an arbitrary thread. This requires some effort though as
the parameters passed to the handlers must remain around until the invocation is
complete. In order to know when the invocation is complete it is necessary
to call EndInvoke. This method will block until the invocation
is complete.
Refer to MSDN for a complete example. Search for Calling Synchronous Methods
Asynchronously.
- What is the difference between a delegate and the MulticastDelegate? Answer
Technically nothing. MulticastDelegate was originally created
to differentiate (and optimize) situations where there was a single target method
vs. multiple. Before the final release they were effectively combined into
one. All delegates derive from MulticastDelegate now.
Note that this class, along with Delegate, are one of the few classes
that you can not directly derive from.
- How can you update the UI from an event raised on another thread? Answer
You can't directly. You'll end up having to marshal the request across threads.
A control's Invoke and InvokeRequired members
can be used for that. In v2 a better option, when possible, is to use
BackgroundWorker which does its work on a backgroun thread but then
automatically marshals events to the UI thread.
Refer to my article
Updating the UI On Arbitrary Threads
for how to do this.
- In what order are event handlers called when an event is raised? Answer
Technically it is implementation defined. Some people assume that it is the
order in which the handlers were added to the event. Some implementations
might do this but you should never rely on it.
- How can you cancel an event? Answer
Technically you can't. Once an event is raised on handlers for the event will
be called. If you want to allow your handlers to be able to cancel an operation
then you will have to program it manually. The CancelEventArgs
class can be used to allow a handler to notify you if an event should be cancelled
or not. This class exposes the Cancel property which can
be set in a handler. When set the operation that raised the event should be
cancelled. This is commonly done for pre-events such as prior to closing a
form or prior to selecting a node in a tree. Note that all the handlers are
still called so each handler should check the cancel flag before performing any
major work.
- What happens if an exception is thrown by an event handler? Answer
In general, unless you implement it yourself, if a handler throws an exception then
the exception is propagated back to whoever raised the exception. Any handler
that had not yet been called will be ignored. Here is some sample code (that
assumes that handlers are called in the order in which they are inserted).
private event EventHandler DoWork;
private void DoSomeWork
{
DoWork += Handler1;
DoWork += Handler2;
DoWork += Handler3;
try
{
DoWork(this,
EventArgs.Empty);
} catch (Exception ex)
{ };
}
private void Handler1 ( object sender,
EventArgs e )
{ Debug.WriteLine("Handler1"); }
private void Handler2 ( object sender,
EventArgs e )
{
Debug.WriteLine("Handler2");
throw new Exception("Crash it.");
}
private void Handler3 ( object sender, EventArgs e )
{ Debug.WriteLine("Handler3");
}
As an aside be aware that in C# the event itself can be null. Therefore you
should always check for null prior to raising an event. If you do not then
an exception will be raised even though there are no handlers. In VB this
is not necessary.
private void OnDoWork ()
{
EventHandler hdlr = DoWork;
if (hdlr != null)
hdlr(this, EventArgs.Empty);
}