Delegates
Delegates are type-safe function pointers. They allow you to treat methods as objects. Delegates are fundamental to event handling, callback mechanisms, and asynchronous programming in the .NET Framework.
What are Delegates?
A delegate declaration defines a type that can reference any method with a compatible signature (return type and parameter types). Once declared, delegate instances can be created that point to specific methods. These instances can then be invoked as if they were the methods themselves.
Declaring a Delegate
You declare a delegate using the delegate
keyword:
public delegate void MyDelegate(string message);
This declares a delegate type named MyDelegate
that can reference any method that takes a single string
argument and returns void
.
Instantiating a Delegate
To create an instance of a delegate, you can assign a compatible method to it:
// Assuming a method like:
// public void DisplayMessage(string msg) { Console.WriteLine(msg); }
MyDelegate handler = DisplayMessage;
You can also use a lambda expression:
MyDelegate lambdaHandler = (msg) => Console.WriteLine($"Lambda: {msg}");
Invoking a Delegate
Invoking a delegate is straightforward. You can call it like a method:
handler("Hello, Delegates!");
lambdaHandler("Using Lambdas");
Multicast Delegates
Delegates can represent a chain of methods. This is known as a multicast delegate. You can add methods to a delegate instance using the +
operator and remove them using the -
operator.
Creating a Multicast Delegate
public delegate void SimpleDelegate();
public void Method1() { Console.WriteLine("Method 1"); }
public void Method2() { Console.WriteLine("Method 2"); }
SimpleDelegate multicastDelegate = Method1;
multicastDelegate += Method2; // Add Method2
multicastDelegate -= Method1; // Remove Method1 (optional)
Invoking a Multicast Delegate
When a multicast delegate is invoked, all methods in its invocation list are called sequentially:
multicastDelegate();
// Output: Method 2
Common Use Cases
- Event Handling: Delegates are the backbone of the event model in .NET. Publishers use delegates to define events, and subscribers use delegates to hook up their event handler methods.
- Callbacks: Passing a delegate to a method allows that method to call back to the original code at a later point, for example, to notify it of completion or to provide results.
- Asynchronous Operations: Delegates are used extensively in asynchronous programming patterns, such as
ThreadPool.QueueUserWorkItem
and the `BeginInvoke`/`EndInvoke` pattern.
Example: Event Handling with Delegates
Consider a simple button click event:
// Define a delegate for the event
public delegate void ButtonClickHandler(object sender, EventArgs e);
public class Button {
// Define the event using the delegate
public event ButtonClickHandler Click;
public void OnClick() {
// If there are any subscribers, invoke the delegate
Click?.Invoke(this, EventArgs.Empty);
}
}
public class MainForm {
public void SubscribeToButton(Button btn) {
btn.Click += new ButtonClickHandler(MyButton_Click);
}
private void MyButton_Click(object sender, EventArgs e) {
Console.WriteLine("Button was clicked!");
}
}
Built-in Delegate Types
The .NET Framework provides several generic delegate types that are commonly used:
Action<T1, T2, ...>
: Represents a method that takes one or more parameters and returnsvoid
.Func<TResult>
,Func<T1, TResult>
, ...: Represents a method that takes zero or more parameters and returns a value. The last type parameter is always the return type.Predicate<T>
: A specializedFunc<T, bool>
delegate used for methods that return a boolean value based on an input parameter.
Using these built-in delegates often simplifies code and reduces the need for custom delegate declarations.
Example: Using Action and Func
// Using Action
Action<string> greet = (name) => Console.WriteLine($"Hello, {name}!");
greet("World");
// Using Func
Func<int, int, int> add = (a, b) => a + b;
int sum = add(5, 3);
Console.WriteLine($"Sum: {sum}");