Delegates are one of the most versatile, powerful, and often misunderstood features of C#. They are integral to the elegant .NET event system. If you've ever written a custom event, you may have used a delegate, possibly without really understanding the particulars of it. With the introduction of Generics, delegates finally got the respect they deserve.
In spite of all this, delegates are also probably one of the most neglected features of the language. This may be because they crop up at various times with little introduction or explanation and so little understanding. As of this writing, there really seems to be no simple, beginner level guides to their use. Hopefully this article will help fill that gap.
Here's a simple program that prints out some information about an array:
csharp
void PrintArrayInfo(int[] list) {
Console.Write("Number List:");
foreach (int item in list) {
Console.Write(" " + item);
}
Console.WriteLine();
Console.Write("Even Number List:");
foreach (int item in list) {
if (item % 2 == 0) {
Console.Write(" " + item);
}
}
Console.WriteLine();
Console.Write("Half Number List:");
foreach (int item in list) {
Console.Write(" " + item/2);
}
Console.WriteLine();
}
We see three chunks of basically the same code. At this point, our inner programmer should be trying to figure out the common elements in those three chunks. Their should be a way... but there's not. Because the differing element is
inside the repeating code.
We're going to identify the elements inside and give them their own methods.
csharp
void ShowItem(int item) {
Console.Write(" " + item);
}
void ShowEvenItem(int item) {
if (item % 2 == 0) {
Console.Write(" " + item);
}
}
void ShowHalfItem(int item) {
Console.Write(" " + item/2);
}
void PrintArrayInfo(int[] list) {
Console.Write("Number List:");
foreach (int item in list) {
ShowItem(item);
}
Console.WriteLine();
Console.Write("Even Number List:");
foreach (int item in list) {
ShowEvenItem(item);
}
Console.WriteLine();
Console.Write("Half Number List:");
foreach (int item in list) {
ShowHalfItem(item);
}
Console.WriteLine();
}
Well, that didn't seem to gain us anything. In fact, it looks worse than when we started. But it does illustrate a point. If only we could somehow pass a reference to each of those methods, we'd be onto something. This is where delegates come in.
A delegate is simply a place holder for a method. You can think of it as a method type or a method interface. Here's what it looks like in action.
csharp
// We declare our delegate
// it's called ProcessListItem and defines a method
// that takes one parameter and has a void return value
delegate void ProcessListItem(int item);
// This method takes a name, a list, a method ( delegate )
void ProcessList(string name, int[] list, ProcessListItem processor) {
Console.Write(name + ":");
foreach (int item in list) {
// we use the label "processor"
// defined as a delegate of ProcessListItem
// we call it as if it were simply a method.
processor(item);
}
Console.WriteLine();
}
void PrintArrayInfo(int[] list) {
// make calls, passing predefined methods you want to use
// you can use just the name
ProcessList("Number List", list, ShowItem);
ProcessList("Even Number List", list, ShowEvenItem);
// we can also explicitly reference the delegate you're
// using by name, with this syntax
ProcessList("Half Number List", list,
new ProcessListItem(ShowHalfItem)
);
}
This is neat, but it gets better. One thing you may be thinking is what a bother it seems to have to define all those methods for passing. Often we just want a one off. For this, we can use an anonymous delegate.
csharp
ProcessList("Odd Number List", list, delegate(int item) {
if (item % 2 != 0) {
Console.Write(" " + item);
}
});
But anonymous delegates offer more than simply saving a method call. They are the secret to doing to unexpected and very useful things. Here's an example:
csharp
int maxNum = list[0];
ProcessList("Finding Max", list, delegate(int item) {
Console.Write(".");
if (item > maxNum) {
maxNum = item;
}
});
Console.WriteLine("Max = " + maxNum);
Look at the above carefully. We define a local variable maxNum. Inside our anonymous delegate we reference that variable because it's in our current scope! Without breaking the rules of our delegate method pattern, we have effectively managed to pass extra arguments in and out of it.
That's it for now. We'll explore that last bit in detail when we talk about the powerful use of delegates in the Generic collections.
Here's a complete example of the above in one place. ( Thanks for RodgerB for the catch. )
csharp
using System;
namespace DelegateDemo {
/// <summary>
/// A simple demonstration of using delegates
/// </summary>
class Program {
// We declare our delegate
// it's called ProcessListItem and defines a method
// that takes one parameter and a a void return value
private delegate void ProcessListItem(int item);
// This method takes a name, a list, a method ( delegate )
private void ProcessList(string name, int[] list, ProcessListItem processor) {
Console.Write(name + ":");
foreach (int item in list) {
// we use the label "processor"
// defined as a delegate of ProcessListItem
// we call it as if it were simply a method.
processor(item);
}
Console.WriteLine();
}
public void PrintArrayInfo(int[] list) {
// you can use just predefined methods
ProcessList("Number List", list, this.ShowItem);
ProcessList("Even Number List", list, this.ShowEvenItem);
// you can also explicitly reference the delegate you're
// using by name, with this syntax
ProcessList("Half Number List", list,
new ProcessListItem(ShowHalfItem)
);
// you can use an anonymous delegate
ProcessList("Odd Number List", list, delegate(int item) {
if (item % 2 != 0) {
Console.Write(" " + item);
}
});
// with an anonymous delegate,
// you can also process other values in the local scope
int maxNum = list[0];
ProcessList("Finding Max", list, delegate(int item) {
Console.Write(".");
if (item > maxNum) {
maxNum = item;
}
});
Console.WriteLine("Max = " + maxNum);
}
private void ShowItem(int item) {
Console.Write(" " + item);
}
private void ShowEvenItem(int item) {
if (item % 2 == 0) {
Console.Write(" " + item);
}
}
private void ShowHalfItem(int item) {
Console.Write(" " + item / 2.0);
}
static void Main(string[] args) {
Program pgm = new Program();
// test data
pgm.PrintArrayInfo(new int[] { 2, 5, 8, 9, 5, 0, 8, 6, 7, 3 });
Console.ReadLine();
}
}
}
/* Results:
Number List: 2 5 8 9 5 0 8 6 7 3
Even Number List: 2 8 0 8 6
Half Number List: 1 2.5 4 4.5 2.5 0 4 3 3.5 1.5
Odd Number List: 5 9 5 7 3
Finding Max:..........
Max = 9
*/
This post has been edited by baavgai: 12 Oct, 2008 - 04:11 AM