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

Md Khairul AlamMd Khairul Alam
3 min read

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.

0
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

Md Khairul Alam
Md Khairul Alam