C# Delegates
Passing methods as parameters for flexible code
🎯 What are Delegates?
Delegates are type-safe function pointers that reference methods. They allow you to pass methods as parameters, enabling callbacks and event handling in your applications with flexibility and safety.
// Define a delegate
delegate void MessageDelegate(string message);
// Method that matches delegate signature
void ShowMessage(string msg)
{
Console.WriteLine("Message: " + msg);
}
// Use the delegate
MessageDelegate del = ShowMessage;
del("Hello!"); // Calls ShowMessage
Output:
Message: Hello!
Delegate Concepts
Basic Delegates
Reference to a method
delegate void MyDelegate
(string s);
Multicast
Call multiple methods at once
del += Method1;
del += Method2;
Action & Func
Built-in delegate types
Action<string> action;
Func<int, bool> func;
Lambda
Inline anonymous methods
del = (x) =>
Console.WriteLine(x);
🔹 Basic Delegate Usage
A delegate is a type-safe function pointer that defines a method signature and can reference any matching method. It allows methods to be passed as parameters, stored in variables, and invoked dynamically. This enables callback mechanisms, event handling, and flexible algorithm design (like passing comparison logic to a sort method). Delegates are the foundation for events and LINQ in C#, providing the ability to treat code as data and implement powerful patterns like strategy or observer.
using System;
// Define a delegate type
delegate void PrintDelegate(string message);
class Program
{
// Methods that match the delegate signature
static void PrintToConsole(string msg)
{
Console.WriteLine("Console: " + msg);
}
static void PrintWithStars(string msg)
{
Console.WriteLine("*** " + msg + " ***");
}
static void Main()
{
// Create delegate instances
PrintDelegate printer;
// Point to first method
printer = PrintToConsole;
printer("Hello World");
// Point to second method
printer = PrintWithStars;
printer("Hello World");
// Pass delegate as parameter
ExecutePrint(PrintToConsole, "Passed as parameter");
}
static void ExecutePrint(PrintDelegate del, string message)
{
del(message);
}
}
Output:
Console: Hello World
*** Hello World ***
Console: Passed as parameter
🔹 Multicast Delegates
Multicast delegates in C# allow a single delegate instance to reference multiple methods. When the delegate is invoked, all attached methods are called in sequence, making them ideal for implementing event handling, logging systems, or notification chains. For instance, a system update event might simultaneously trigger a log entry, send an email, and remove an old SMS alert. Use the `+=` operator to add methods and `-=` to remove them, ensuring flexible and maintainable callback management within your applications.
using System;
delegate void NotifyDelegate(string message);
class Program
{
static void SendEmail(string msg)
{
Console.WriteLine("Email sent: " + msg);
}
static void SendSMS(string msg)
{
Console.WriteLine("SMS sent: " + msg);
}
static void LogMessage(string msg)
{
Console.WriteLine("Logged: " + msg);
}
static void Main()
{
// Create multicast delegate
NotifyDelegate notify = SendEmail;
notify += SendSMS; // Add second method
notify += LogMessage; // Add third method
Console.WriteLine("Sending notification...\n");
notify("System update available");
// Remove a method
Console.WriteLine("\nRemoving SMS notification...\n");
notify -= SendSMS;
notify("New message");
}
}
Output:
Sending notification...
Email sent: System update available
SMS sent: System update available
Logged: System update available
Removing SMS notification...
Email sent: New message
Logged: New message
🔹 Action and Func Delegates
The Action and Func built-in delegate types work seamlessly with lambda expressions for different purposes. Action represents a void method, used for side effects like printing: Action<string> greet = name => Console.WriteLine($"Hello, {name}!"); outputs "Hello, Alice!". Func represents a method returning a value, used for computations: Func<int, int, int> add = (a, b) => a + b; returns 20 for 10+10. They encapsulate behavior inline, promoting functional programming patterns in C#.
using System;
class Program
{
static void Main()
{
// Action - no return value
Action<string> greet = (name) =>
{
Console.WriteLine("Hello, " + name + "!");
};
greet("Alice");
// Action with multiple parameters
Action<string, int> displayInfo = (name, age) =>
{
Console.WriteLine($"{name} is {age} years old");
};
displayInfo("Bob", 30);
// Func - returns a value
Func<int, int, int> add = (a, b) => a + b;
int sum = add(5, 3);
Console.WriteLine("Sum: " + sum);
// Func with complex logic
Func<string, bool> isLongName = (name) =>
{
return name.Length > 5;
};
Console.WriteLine("Is 'Alexander' long? " + isLongName("Alexander"));
Console.WriteLine("Is 'Bob' long? " + isLongName("Bob"));
}
}
Output:
Hello, Alice!
Bob is 30 years old
Sum: 8
Is 'Alexander' long? True
Is 'Bob' long? False
🔹 Lambda Expressions with Delegates
Lambda expressions offer a concise, inline way to define anonymous methods for delegates. Written as `(parameters) => expression`, they simplify operations like filtering lists or transforming data without declaring full methods. For example, `numbers.Where(n => n % 2 == 0)` quickly extracts even numbers. Lambdas are especially useful with `Action` and `Func` delegates for short, one-off operations, enhancing code readability and reducing boilerplate in scenarios such as event handlers or LINQ queries.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Lambda with Func delegate
Func<int, bool> isEven = x => x % 2 == 0;
var evenNumbers = numbers.Where(isEven);
Console.WriteLine("Even numbers: " + string.Join(", ", evenNumbers));
// Inline lambda
var oddNumbers = numbers.Where(x => x % 2 != 0);
Console.WriteLine("Odd numbers: " + string.Join(", ", oddNumbers));
// Lambda with multiple statements
Action<int> processNumber = (n) =>
{
int square = n * n;
Console.WriteLine($"{n} squared is {square}");
};
Console.WriteLine("\nProcessing numbers:");
processNumber(3);
processNumber(5);
// Lambda for transformation
Func<string, string> formatName = name =>
$"Mr./Ms. {name.ToUpper()}";
Console.WriteLine("\n" + formatName("alice"));
Console.WriteLine(formatName("bob"));
}
}
Output:
Even numbers: 2, 4, 6, 8, 10
Odd numbers: 1, 3, 5, 7, 9
Processing numbers:
3 squared is 9
5 squared is 25
Mr./Ms. ALICE
Mr./Ms. BOB
🔹 Delegates as Callbacks
Delegates are commonly used to implement callback patterns, enabling methods to notify callers upon completion. By passing a delegate as a parameter, an asynchronous operation can signal when it finishes, passing back results or status. This pattern is foundational for async programming and event-driven architectures. For instance, a data processor might accept an `Action
using System;
class DataProcessor
{
// Method that accepts a callback delegate
public void ProcessData(int[] data, Action<string> callback)
{
Console.WriteLine("Processing data...");
int sum = 0;
foreach (int num in data)
{
sum += num;
}
// Call the callback when done
callback($"Processing complete! Sum = {sum}");
}
}
class Program
{
static void Main()
{
DataProcessor processor = new DataProcessor();
int[] numbers = { 1, 2, 3, 4, 5 };
// Pass callback as lambda
processor.ProcessData(numbers, (result) =>
{
Console.WriteLine("Callback received: " + result);
});
// Pass callback as method
processor.ProcessData(numbers, OnProcessComplete);
}
static void OnProcessComplete(string message)
{
Console.WriteLine("Method callback: " + message);
}
}
Output:
Processing data...
Callback received: Processing complete! Sum = 15
Processing data...
Method callback: Processing complete! Sum = 15
🔹 Delegate Types Summary
Understanding when to use each delegate type:
Delegate Types:
- Custom Delegate: When you need a specific, reusable signature
- Action<T>: Methods with parameters but no return value (up to 16 parameters)
- Func<T, TResult>: Methods that return a value (last type parameter is return type)
- Predicate<T>: Methods that return bool (used for testing conditions)
- Lambda Expressions: Short, inline anonymous methods
// Examples of each type
delegate int CustomDelegate(int x, int y);
Action<string> action = s => Console.WriteLine(s);
Func<int, int, int> func = (a, b) => a + b;
Predicate<int> predicate = x => x > 0;