Mastering Delegates, Actions, Funcs, and Events in C#


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.
Subscribe to my newsletter
Read articles from Md Khairul Alam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
