A delegate is a type that references a method.
A data structure that refers to a static method or to a class instance and an instance method of that class.
A delegate declaration defines a reference type that can be used to encapsulate a method with a specific signature. A delegate instance encapsulates a static or an instance method. Delegates are roughly similar to function pointers in C++; however, delegates are type-safe and secure.
Delegates are crucial to understand some of the basic functionality in .Net. Delegates form the basis for events and event handling, LINQ, and many other parts of the system. However, delegates can be a tough thing to understand. There are several problems understanding what delegates are and how they work. So, before we get into the nitty gritty, let’s start with things we already DO understand so we have something to which to relate delegates.
In order to understand how I am going to explain delegates, you need to understand the difference between a class and an object. If you are not sure about that difference, what I am about to explain probably isn’t going to make much sense. Come and take a good object-oriented class from us, such as MS6367 and I’ll be glad to teach these concepts to you.
Assuming you are comfortable with the difference between a class and an object, let’s press on. Here’s some definitions that Microsoft has for delegates:
Are you clear now? Hmm, thought not. That’s why you have people like me to cut through the jargon.
When we do object-oriented programming, we understand the difference between a class and an object. A class is what defines the fields, properties, methods, and events that an object will have once the class is instantiated (Note: I am ignoring Shared (VB)/static (C#) declarations for now). In order to use the fields/properties/methods/events we define in a class, we need to instantiate the class into an object using the New (VB)/new (C#) operator. When we do so, we understand that we declare a variable to hold the memory address of the object that is going to be created. In the example below, the first line declares the variable, called “acct”, that is going to hold the memory reference to the created object. The second line instantiates the class into an object, and stores the memory reference to that object in the variable “acct”.
Example 1 – VB
Example 1 – C#
One of the difficulties with delegates is that there are two parts to a delegate: the delegate definition, and the delegate instance. In the object-oriented world, we use the word class to refer to the definition, and object to refer to the instance. When using delegates, we use the same term delegate to refer to both the definition and the instance. In this article, I will try to maintain the distinction between the delegate definition and the delegate instance.
In Example 2, I am going to write a simple program that iterates over a collection of integers and turn each integer into a string, placing each converted item into a second collection.
Example 2 – VB
Example 2 – C#
Note: I know I could have written this example with one loop instead of two. Be patient, my young Padawan. Also, note that there are absolutely NO DELEGATES in this example. This is the basis from which we start our journey to understanding delegates.
The first thing we are going to do in our example program is to write a delegate definition. When we write a delegate definition, the important thing is that the number and types of parameters (the method’s signature), as well as the return type, are the same in the delegate definition as the method which is going to be delegated. We are going to create a delegate for the Transform method, so our delegate definition is going to have to accept a single parameter of datatype Integer/int, and return a String/string. The first line in the next example defines a delegate definition, which I have conveniently named DelegateDef. Other than the first line, the example is exactly the same as the previous example.
Example 3 – VB
Example 3 – C#
Next, we are going to create a delegate instance. In the following example, I instantiate the delegate and use it instead of calling the Transform method directly. Notice first the definition of the variable del, assigned the datatype DelegateDef. Notice also the way I call the Transform method in the first For/for loop – I am calling it using the del variable, not calling Transform directly.
Example 4 – VB
Example 4 – C#
Effectively, delegates are a variable which holds a memory address of a method. We can execute that method by invoking the memory reference to that method, passing the same parameters and getting the same return type as if we had run the method directly. It’s a little like having a garage door opener. Instead of being in the garage and pushing the button on the wall (invoking the method directly), we push the button on the remote control unit (invoke the delegate). In either case, the garage door opens (the method executes).
At this point my students usually stop me and inform me in no uncertain terms that this is stupid, that I have just written a bunch of extra lines of code that just make things harder to read. Surprisingly enough, I completely agree. In my defense I must say that these examples are put together to explain what delegates are, not how we typically use them. We don’t use them in this way. You’ll see an example of the power of delegates a bit later. Be patient, my young Padawan.
Using the Invoke method on a delegate instance is not the only way to run the delegated method. You can invoke the delegate by treating it just as if it were the method itself, as shown. If you can’t spot the difference between this example and the last, examine the first For/for loop.
Example 5 – VB
Example 5 – C#
To wind up our discussion, now that you know what delegates are, I need to show you how to use them. To do so, I am going to use a pre-defined delegate definition that has been supplied to us in the .Net class library. It is called Converter<T, U>, and it is used to create a delegate instance to a method that converts from one datatype to another. Notice that in Example 6 below, we have removed our delegate definition named DelegateDef because we are using the built-in Converter<T, U> delegate definition.
Example 6 – VB
Example 6 – C#
Finally, we get to the power of delegates. Now that we have a delegate instance of datatype Converter<T, U>, we can pass the delegate as a parameter. In the following example, we have eliminated the first For/for loop. Instead, we call the ConvertAll Shared/static method from the Array class. Notice we are passing del, our delegate instance, as the second parameter to the ConvertAll method.
What happens is that the ConvertAll method has its own internal For/for loop to loop over all the elements of the numbers() array. What it does internally is invoke the Transform method, via the delegate instance named del, to change each member of the array from an integer to a string. In essence, it is running the Transform method by remote control. Take awhile to look at this example and get used to the idea of passing a delegate instance as a parameter.
Example 7 – VB
Example 7 – C#
You’ve seen in this article that understanding what delegates are is not terribly challenging. Once we understand the difference between a delegate definition and a delegate instance, understanding what delegates is easy enough. What is challenging is understanding what we can do with delegates once we have them instantiated. Once we have a delegate instance, we can pass that delegate instance as a parameter, allowing other parts of our program to run methods defined in this code file.
This ability to pass delegates around in our programs allows for many unique abilities. Example 7 is just a simple example of the power of delegates. Hopefully this article gives you a better idea of what delegates are and how to use them. In particular, the LINQ technology (which we teach in our C# New Technology 2008, Data Access in Visual Studio 2010, and ADO.Net courses) makes extensive use of delegates to perform its awesome capability.