This post provides introductory information on using delegates. It is the first in a three or four part series that will cover:
- Anonymous Delegates
- Anonymous Delegates that use generics
The overall goal of the series is to explain lambdas. I have however, broken this post out as a separate stand-alone piece that should be of interest to anyone who wants to understand delegates.
Delegates and Events
Whether you know it or not, you have probably used delegates thousands of times in your programming career. If you have ever written any code in Delphi, VB or C# then, then you have almost certainly written event handlers. One drops a button on a form, double clicks on the button, and then writes some code to handle a button click event. As the following declaration shows, those kinds of events rely on delegates:
public delegate void EventHandler(object sender, EventArgs e);
The event itself is not a delegate. It uses a delegate and then adds some extra functionality of its own. If, however, you understand something about how to use an event, then you are well on your way to understanding delegates.
If a delegate is closely related to an event, but it is not an event, then what is it? How do we define the word delegate?
If you are an experienced programmer, then I might help if I say that a delegate is a C# implementation of a method or function pointer. In C#, however, we don't usually work directly with pointers. Instead, delegates are classes. You can use and pass around this delegate class around much as you would a function pointer. Unlike a function pointer, however, delegates are classes and are therefore type safe.
If you don't understand function pointers, then you are still no closer to understanding delegates. So lets go back one step further and see if there is a simple way to explain delegates without talking about function pointers.
Delegates allow you to delegate a particular action or function in your program to another chunk of code. When you click on a button, the button delegates the handling of the event to an object in your form or dialog. The same thing happens with ordinary delegates. If you pass a delegate to a method as a parameter, then that method can execute a predefined task by calling the delegate. It can delegate the task to the delegate -- if you follow me.
Delegating an Action
So that I can come up with the simplest example possible, I'll now give you a somewhat tongue in cheek scenario in which one could choose to use delegates. Suppose I were working in a team with a programmer who shows a lot of promise, but who is still new to programming. Very new. He's a hard working, so I like to assign him tasks, but I don't want the task to be too complicated. This programmer has a hard time remembering the difference between the equality operator (==) and the assignment operator (=). As a result, I might say something like this to him: "I want you to write a method called PerformCalculation. In that method, when it comes time to figure out whether the result of your calculation is equal to the number nine, I want you to delegate that task to me. To help you out, I will pass in a delegate called IsNine that will perform the calculation to determine if the result is equal to nine. It will return true if a number is equal to nine, otherwise it will return false. Do you understand?"
Listing 1 is the finished program that my friend and I created.
Listing 1: A simple delegate. The formatting here makes it hard to blockcopy this code directly, but I will attach the project to this post.
1: using System;
2: using System.Collections.Generic;
3: using System.Text;
5: namespace ConsoleDelegate
7: class Program
9: public delegate Boolean IsNineDelegate(int i);
11: public Boolean IsNiner(int i)
13: return i == 9;
16: public void ShowMe()
18: Boolean result = PerformCalculation(IsNiner, 3, 6);
22: public Boolean PerformCalculation(IsNineDelegate isNine, int value1, int value2)
24: int result = value1 + value2;
25: return isNine(result);
28: static void Main(string args)
30: Program program = new Program();
The first step is to write a method that will calculate whether a number is equal to nine. On lines eleven through fourteen, in the method called IsNiner, I complete that task.
My next job is to pass that method into the PerformCalculation function that I'm asking my friend to write. That turns out to be a two step process:
- Declare the delegate type
- Pass in the delegate instance to the user.
On line 9 the delegate is declared and given the name IsNineDelegate. If you remove the word delegate from line 9 and replace it with an "r", you will see that it's signature exactly matches the declaration of the IsNiner method on line 11. In particular, it is a public method that returns a Boolean value and accepts an integer as a parameter.
Now look down on line 22. The first parameter to the PerformCalculation method is a delegate of type IsNineDelegate. This means that you can pass in a method of type IsNineDelegate to this method. Look back at line 18 and you will see that I do in fact pass in the IsNiner method as the first parameter to the PerformCalculation method. Then on line 25, the IsNine delegate is actually called.
You now have enough information to write your own delegates. In this section I'll give you some additional information that is not absolutely necessary to know, but which you might find useful.
I've said that the signature of the delegate type and the delegate instance need to be the same. They must return the same type and pass in the same types as parameters. However, you don't have to give the variables in the parameters the same name. For instance, the following is still valid code:
1: public delegate Boolean IsNineDelegate(int value);
3: public Boolean IsNiner(int i)
5: return i == 9;
8: public void ShowMe()
10: Boolean result = PerformCalculation(IsNiner, 3, 6);
In this code the delegate type declares the formal parameter as int nine, but the delegate instance declares the corresponding formal parameter as int i. This is completely legal, and the code on line 10 will still compile and run smoothly with no warnings and no errors.
On line 10 above, I pass in IsNiner directly. The following code would also be valid:
1: public void ShowMe()
3: IsNineDelegate myDelegate = IsNiner;
4: Boolean result = PerformCalculation(myDelegate, 3, 6);
As you can see, I've declared an instance of the IsNiner delegate called myDelegate. In this case, it is really just so much wasted motion, but there are times when it is useful to do something like this. (Notice that you don't need to call new when creating an instance of the IsNineDelegate on line 3. That was necessary in .NET 1.1, but is not necessary any longer.)
You now know enough about delegates to be dangerous. I have not, however, told you enough to make you an expert in this subject. For one thing, I've said little about the connection between the code that I've shown you above and events.
Events are indeed a kind of delegate, but the approach the subject from the opposite point of view. They also have some special properties.
We have passed in a delegate to the PerformCalculation method in order to provide a helper method as a service. Events take the opposite approach. They are the ones that provide the service to the caller. You pass in an event to a method, and then that method helps out the caller by executing the event at the appropriate time. By doing this, it notifies the call that an event has occurred.
By implementing the observer pattern (aka as the publish-subscribe pattern, you can use delegates as a kind of make-shift event system. However, most developers use events when they want that functionality.
When you start thinking about events, it is easy to imagine the case in which two methods or classes need to be notified when a particular event occurs. This functionality can be supported through something called a multicast delegate. All delegates are actually multicast delegates, but you don't need to know that in order to work with the code I've been showing you.
Finally, it is important to note that true events add some restrictions to what you can do with multicast delegates. Events are multicast delegates, but there are restrictions on how they can be created and when they can only be fired within certain scopes.
The point I'm trying to drive home here is that I have not told you all that you need to know be an expert on the subject of delegates. However, I have told you enough so that you can start using delegates in your own programs. Furthermore, I've taught you enough so that you have the background necessary to learn how to create anonymous delegates, which is a subject I will cover in another post.
I should probably also stress a point I made earlier, which is that C# delegates are really classes. If you use the ILDASM utility that ships with at least some versions of Visual Studio, you can look at the simple program found in listing one and find the following class declaration:
1: .class auto ansi sealed nested public IsNineDelegate
2: extends [mscorlib]System.MulticastDelegate
4: } // end of class IsNineDelegate
Notice that IsNineDelegate is a sealed public class, and that it extends System.MulticastDelegate. In other words, delegates are really classes. They look and act like function pointers, but underneath the hood they are really classes. For most developers, this fact is just a useless piece of trivia. If it has any importance, it is just to highlight the fact that C# delegates are type safe.
In this post I've covered the basic facts you need to know to use delegates. You have seen how to declare a delegate, how to create a delegate instance, and how to pass it to a method which can consume the delegate.
I also went to pains to inform you about the limits of this post. It tells you the basic facts you need to know about delegates if you want to understand anonymous delegates. If you understand anonymous delegates, then you can understand anonymous generic delegates. Once you understand anonymous generic delegates, then you can understand lambdas. That's where all this heading. if you understand what I have written here, then you are well on your way to understanding lambdas.
If you don't care anything at all about lambdas, then at least you now have the basic information necessary to write delegates. If you want advanced information on this subject, then you now know to go off looking for information on multicast delegates, and on how events differ from delegates. By adding the event keyword to a delegate, you restrict what you can do with a delegate in certain important ways that help prevent you from falling into devilish traps that event developers find particularly alluring.