Delegates, Actions, Funcs, Predicates, and Events are fundamental concepts in C# that allow flexible, type-safe method references and decouple components in a modern .NET application. These concepts are heavily used in LINQ, asynchronous programming, event-driven designs, and Clean Architecture. In this article, we will cover:
Delegates and their usage
Multicast Delegates
Action, Func, and Predicate
Events and Event Handlers
Event examples and real-world applications
This guide will prepare you for senior .NET developer and architect interviews, where in-depth knowledge of these topics is crucial.
Understanding Delegates
A delegate is a type that references a method with a specific signature. It allows passing methods as parameters, enabling callback mechanisms and event-driven programming.
Basic Delegate Example
public delegate void Notify(string message);
class Program
{
static void Main()
{
Notify notify = DisplayMessage;
notify("Hello, Delegates!");
}
static void DisplayMessage(string message)
{
Console.WriteLine(message);
}
}
In C#, we can use Action, Func, and Predicate instead of defining custom delegates.
Multicast Delegates
A multicast delegate can reference multiple methods and invoke them sequentially.
public delegate void LogHandler(string message);
class Logger
{
public static void LogToConsole(string message) => Console.WriteLine("Console Log: " + message);
public static void LogToFile(string message) => Console.WriteLine("File Log: " + message);
}
class Program
{
static void Main()
{
LogHandler log = Logger.LogToConsole;
log += Logger.LogToFile;
log("System started"); // Calls both methods
}
}
Multicast delegates are useful for logging, notifications, and broadcasting messages in an application.
Action, Func, and Predicate
To simplify delegates, .NET provides built-in generic delegates:
Action<T> – Returns void
Func<T, TResult> – Returns a value
Predicate<T> – Returns a boolean
Action Example
Action<string> print = message => Console.WriteLine("Message: " + message);
print("Hello, World!");
Func<T, TResult> Example
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(10, 20));
Predicate Example
List<int> numbers = new() { 1, 2, 3, 4, 5, 6, 7, 8 };
Predicate<int> isEven = num => num % 2 == 0;
List<int> evenNumbers = numbers.FindAll(isEven);
Console.WriteLine(string.Join(", ", evenNumbers)); // Output: 2, 4, 6, 8
When to Use These?
Action<T> is great for callbacks and void-returning operations
Func<T, TResult> is best for data processing and transformations
Predicate<T> is useful in LINQ queries and filtering
Events and Event Handlers
Events provide a publisher-subscriber model in C#, which is crucial for loosely coupled architectures.
Event Example Using Delegate
public delegate void ProcessCompletedHandler();
public class Process
{
public event ProcessCompletedHandler ProcessCompleted;
public void Start()
{
Console.WriteLine("Processing...");
ProcessCompleted?.Invoke();
}
}
class Program
{
static void Main()
{
Process process = new Process();
process.ProcessCompleted += () => Console.WriteLine("Process Finished!");
process.Start();
}
}
Event Example with EventHandler & EventArgs
public class ProcessEventArgs : EventArgs
{
public string Message { get; }
public ProcessEventArgs(string message) => Message = message;
}
public class Process
{
public event EventHandler<ProcessEventArgs> ProcessCompleted;
public void Start()
{
Console.WriteLine("Processing...");
OnProcessCompleted("Process Finished");
}
protected virtual void OnProcessCompleted(string message)
{
ProcessCompleted?.Invoke(this, new ProcessEventArgs(message));
}
}
class Program
{
static void Main()
{
Process process = new Process();
process.ProcessCompleted += (sender, e) => Console.WriteLine("Notification: " + e.Message);
process.Start();
}
}
E-Commerce Notification Example with Action
public class Order
{
public event Action OrderPlaced;
public void PlaceOrder()
{
Console.WriteLine("Order Placed!");
OrderPlaced?.Invoke();
}
}
class NotificationService
{
public static void SendEmail() => Console.WriteLine("Email Sent: Your order has been placed!");
public static void SendSMS() => Console.WriteLine("SMS Sent: Your order has been placed!");
}
class Program
{
static void Main()
{
Order order = new Order();
order.OrderPlaced += NotificationService.SendEmail;
order.OrderPlaced += NotificationService.SendSMS;
order.PlaceOrder();
}
}
Real-World Use Cases
Button Click Handlers in UI frameworks (WinForms, WPF, Blazor)
Messaging systems and pub-sub architectures
Asynchronous processing and event-driven systems
Mastering delegates, actions, funcs, and events is critical for designing scalable, maintainable, and loosely coupled .NET applications. By leveraging modern C# features, you can write cleaner, more efficient, and testable code.