This is part one of a series of posts on events and delegates, a powerful means of implementing
loosely-coupled designs. This part will focus on delegates. You can read part two here.
Introduction
As a developer, I think it took me longer than necessary to get my head
around the code details of the event/delegate paradigm. In retrospect,
I believe this was mainly due to my lack of understanding of the
delegate type, which connects everything together.
Another problem I encountered was a lack of consistency of language
and definitions used by the authors of books and articles on events and
delegates. More on this later.
Publishers + Subscribers = Observer Pattern
There is no need to have a PhD in anonymous methods, function pointers,
multicast versus single cast delegates and the like, in order to get started
with events. What you do need is a basic understanding of the central
piece of the jigsaw: the delegate. Next, you need to get an overview of
the Publish-Subscribe model. Finally, you need to open up your code editor and create a basic event sample for yourself.
Overview
Generally, delegates (or callbacks) are typesafe pointers to methods,
similar to function pointers in C++. We can use delegates to specify
callbacks and also to register handlers for GUI events like button
clicks. Unlike function pointers, which can only contain the address of
a static method, a delegate can refer to instance methods in classes -
this is what makes them tremendously useful. A delegate is like an
interface: the difference is that the delegate declares a single static
or reference method, whereas the interface can declare several methods.
Whenever we want classes to communicate with less coupling than with an
interface, we should use a delegate. In other words, delegates are not
only for use with events!
If we need to specify an action or method in advance and we don't know
which method, or even object, that will perform this action, we should
use a delegate. For example, we can connect a button to a delegate and
at runtime we can resolve that delegate to a particular method to be
run.
Delegates
A delegate is a special type in .NET whose main role in life is
to hold a reference to a method. Delegates are defined with a
signature, much like methods, and can only hold a reference to a method
which matches this signature. We declare a delegate by using the
"delegate" keyword and specifying the signature of the method to which
it holds a reference. The following is a declaration of a delegate that
can reference a method that takes two parameters and does not return a
value:
public delegate void EventHandler(object sender, EventArgs e);
The delegate is a TYPE; think of the entire statement above as a type. A method matching this
signature would look like this:
public void Button_Clicked(object sender, EventArgs e);
In the following example, the delegate called SampleDelegate can
invoke any method that is passed a string parameter and returns an int:
delegate int SampleDelegate(sting str);
We can now initialize a new delegate, like so...
SampleDelegate del = new SampleDelegate(DoSomething);
... and can call the delegate as follows:
del(someString);
Terminology
A final point regarding terminology. When we talk about classes, we
use two separate terms: class and object. With delegates, there is only
a single term. An instance of a delegate class is also known as a
delegate. So, we need to pay attention to context here. I will come
back to the issue of terminology in the next post which will focus on
events.