Events, delegates and lambdas in .NET C# part 1: delegate basics
February 13, 2014 5 Comments
Introduction
I believe that learning the basic features of a popular OO language, such as C# or Java is not too difficult. Obviously you’ll need a positive mindset towards programming in general otherwise you’re bound to fail but that’s true for every discipline. The basic structures found in C#, such as classes, properties, constructors, methods, functions, interfaces and the like are quite straightforward and simple to use even after a couple of months’ worth of training. If you have a Java or C++ background then you can start to be productive with C# after some weeks even.
However, there are some language features that stick out a little as they are slightly more cryptic: events, delegates and lambda expressions. You can certainly come up with others, but my personal experience is that learning these features thoroughly takes more time than the ones listed above.
This series of posts will be dedicated to these language elements of C#.
The building blocks
If you’ve ever done any Windows app development then you must have seen examples of events and event handlers: button click event, list selection changed event, on mouse over event etc. You added an event and an event handler to a button just by double-clicking on it in the visual editor and the following elements were wired together for you in the background:
- Event: notifications originating from objects. These notifications can be used to notify other elements – objects – in the application that may be interested in the event, such as when a new order was placed or a party is about to start. Then the objects listening to those events can perform some work dedicated to them: serve the drinks, order the pizzas, let in the guests etc.
- Event raiser, a.k.a. the Sender: the object that raises the event. They typically don’t know anything about the objects listening to the events raised by themselves
- Event handler: the handler that ‘caught’ the event raised by the button
- Delegate: the glue between the button and the event handler. This is usually hidden in the designer cs file of the Form created by Visual Studio, so you may not even have come across it. Without delegates the event raised would not get to the listening objects and their event handlers
- Event arguments, or EventArgs: the arguments that the Sender is sending to the event handler with the event raised. This can include anything important for the handler, such as the selected item index, the venue for the party, the items that were ordered etc.
Probably the most confusing element in this list is delegates. They are typically described by adjectives such as ‘odd’, ‘strange’ and ‘unusual’. A delegate, also called a function pointer, is a specialised class that derives from the MulticastDelegate base class. As noted above, it connects the event with the event handler. It’s called a function pointer because the event handler will be a function, i.e. a method, and it points at the event handler from the event and wires the event arguments in between.
An event handler will process the data received from a delegate. It is a method with a special signature that must accept a sender of type ‘object’ and an EventArgs parameter or one that’s derived from the EventArgs class. EventArgs objects are normal C# objects with properties. You can easily define your custom event args by deriving from the EventArgs class. You can put in all sorts of properties into your event args that may be interesting for those waiting for the event. A button click event handler may look like this:
public void btnOrderItems_Click(object sender, EventArgs e) { //do something }
A custom event handler can have the following form:
public void OrderPlaced(object sender, OrderPlacedEventArgs e) { //do something with the order }
Delegates
You can create delegates with the ‘delegate’ keyword in C#, example:
public delegate void GraphClicked(GraphType graphType, Point clickCoordinate);
It’s void as nothing is returned, data is only sent one way. In this example the delegate’s name is GraphClicked and it accepts two parameters: a graph type and the click coordinates. This declaration is a blueprint for the method, i.e. the handler, that will receive the data from the delegate. The handler method must match this signature otherwise the code will not compile: it must also receive a graph type and the click coordinates. An example of a matching handler:
public void GraphClicked_Handler(GraphType type, Point coords) { //do something }
Note that the parameter names don’t necessarily match those in the delegate, but the types must match.
The delegate keyword is special in .NET as the compiler does some magic when it sees it declared. It creates another object that inherits from the MulticastDelegate class behind the scenes which in turns derives from the Delegate base class. Multicast delegate means that a single message can be sent to a range of objects through a range of pipelines. Note that you cannot directly derive from the Delegate or the MulticastDelegate classes. You do it indirectly using the delegate keyword and the compiler will do the inheritance trick for you.
The list of pipelines of a multicast delegate is called the Invokation List. The delegates listed here are invoked sequentially.
Now that we have a delegate you can easily instantiate one:
GraphClicked graphClicked = new GraphClicked(GraphClicked_Handler);
So you see a delegate is a class that you can instantiate using the new keyword. Notice that you must specify the handler in the constructor of the delegate. We say that when the graphClicked delegate is invoked then the GraphClicked_Handler method should be called.
You can invoke the graphClicked delegate as follows:
graphClicked(GraphType.Line, new Point(1,1));
So you invoke it like a method and provide the necessary parameters. This adds one delegate item to the invocation list.
There’s nothing stopping you from instantiating several delegates of the same type:
GraphClicked niceGraphClicked = new GraphClicked(NiceGraphClicked_Handler); GraphClicked uglyGraphClicked = new GraphClicked(UglyGraphClicked_Handler);
These delegates point to two different handlers: NiceGraphClicked_Handler and UglyGraphClicked_Handler. What if you want niceGraphClicked not only invoke NiceGraphClicked_Handler but UglyGraphClicked_Handler as well? In other words you want to extend the invocation list of niceGraphClicked to two items: NiceGraphClicked_Handler but UglyGraphClicked_Handler. It couldn’t be simpler:
niceGraphClicked += uglyGraphClicked;
So now when niceGraphClicked is raised…:
niceGraphClicked(GraphType.Cylinder, new Point(2, 3));
…then both NiceGraphClicked_Handler but UglyGraphClicked_Handler will be called in the same order as they were added to the invocation list.
This post has given you the absolute basics of delegates, events and event handlers. We’ll look at a couple of examples from scratch in the next post.
“So you invoke it like a method and provide the necessary parameters. This adds one delegate item to the invocation list.”
This is potentially quite confusing for someone new to the idea of delegates. Invoking a delegate doesn’t add anything to it’s invocation list; it simply calls the members of the invocation list, passing them arguments as necessary. Adding items to an invocation list is done either at the point the delegate is instantiated, by using the + or += member operators, or via a custom registration method you’ve written yourself.
Hi Ben, yes, that and much more will be discussed in the upcoming parts on this topic.
//Andras
Thanks for explaining the delegates and the even handlers. But how to raise an event?
Steven, read the other posts in the series and you’ll find out.
//Andras
Very useful posts and much appreciated. May I return the favor in a small way and suggest a typo correction: “…NiceGraphClicked_Handler but UglyGraphClicked_Handler…” should be “…NiceGraphClicked_Handler and UglyGraphClicked_Handler…”