← Back to Articles         
Delegates and Events

Delegates and Events

Published on 12 Oct 2026     14 min read C#
Delegates, Events & Lambda Expressions
 

C# Delegates and Events – Events vs Delegates and Multicast Delegates

In C#, delegates and events are important concepts used to implement callbacks, notifications, and event-driven programming.

Both are closely related, but they serve different purposes.

We will cover:

  1. What is an event, and how does it differ from a delegate?

  2. What are multicast delegates, and how do they work?

 

1. What is an Event in C#?

1.1 Definition

An event is a mechanism in C# that allows a class to notify other classes when a particular action or change occurs.

Events are built on delegates and follow the Publisher–Subscriber pattern.

  • Publisher: The class that declares and raises an event.

  • Subscriber: The class or method that listens to the event.

  • Event: The notification mechanism connecting the publisher and subscribers.

Real-World Example

Consider a banking application.

When a customer deposits money into an account, several operations may need to happen:

  • Send an SMS notification.

  • Send an email notification.

  • Write an audit log.

Instead of directly calling all these services from the deposit method, the bank account can raise an event.

Other services subscribe to the event and execute their own operations.

Event Execution Flow

             Customer Deposits Money
                       |
                       v
                 BankAccount
                   Publisher
                       |
                       v
              DepositCompleted
                    Event
                       |
            +----------+----------+
            |          |          |
            v          v          v
       SMS Service  Email Service  Audit Service
            |          |          |
            v          v          v
       Send SMS    Send Email    Write Log

This approach reduces direct dependencies between the publisher and subscribers.

 

1.2 Why Do We Need Events?

Without events, one class may need to directly invoke several other classes.

For example:

public void Deposit(decimal amount)
{
    Balance += amount;

    SendSMS();
    SendEmail();
    WriteAuditLog();
}

Problems

  1. The Deposit() method is responsible for multiple operations.

  2. Adding a new notification requires modifying the deposit logic.

  3. The bank account becomes tightly coupled to notification implementations.

  4. Unit testing becomes more complicated.

With events, the deposit method only raises a notification.

public void Deposit(decimal amount)
{
    Balance += amount;

    DepositCompleted?.Invoke(amount);
}

Subscribers decide what action to perform.

Important: Events reduce direct coupling, but they do not automatically provide asynchronous processing, reliable delivery, or transaction guarantees.

 

1.3 How to Declare an Event

Syntax

public event Action<decimal>? DepositCompleted;

Here:

  • public – Allows other classes to subscribe.

  • event – Declares an event.

  • Action<decimal> – Delegate type defining the handler signature.

  • DepositCompleted – Event name.

  • ? – Indicates that the event can be null when no handlers are subscribed.

Important Event Operations

Operator

Purpose

Example

+=

Subscribe to an event

account.DepositCompleted += SendSMS;

-=

Unsubscribe from an event

account.DepositCompleted -= SendSMS;

?.Invoke()

Raise the event

DepositCompleted?.Invoke(1000);

For a normal field-like event, external classes can subscribe and unsubscribe, but cannot directly invoke or replace the event.

 

1.4 Complete Example Program – Banking Deposit Event

using System;

public class BankAccount
{
    public decimal Balance { get; private set; }

    // Declare an event
    public event Action<decimal>? DepositCompleted;

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
        {
            throw new ArgumentException(
                "Deposit amount must be greater than zero.");
        }

        Balance += amount;

        Console.WriteLine($"Deposited: {amount}");

        // Raise the event
        DepositCompleted?.Invoke(amount);
    }
}

public class NotificationService
{
    public void SendSMS(decimal amount)
    {
        Console.WriteLine($"SMS: Deposited {amount}");
    }

    public void SendEmail(decimal amount)
    {
        Console.WriteLine($"Email: Deposited {amount}");
    }
}

public class AuditService
{
    public void WriteLog(decimal amount)
    {
        Console.WriteLine($"Audit: Deposit of {amount} recorded");
    }
}

class Program
{
    static void Main()
    {
        BankAccount account = new BankAccount();

        NotificationService notification = new NotificationService();
        AuditService audit = new AuditService();

        // Subscribe methods to the event
        account.DepositCompleted += notification.SendSMS;
        account.DepositCompleted += notification.SendEmail;
        account.DepositCompleted += audit.WriteLog;

        // Deposit money
        account.Deposit(5000);

        Console.WriteLine($"Balance: {account.Balance}");
    }
}

Output

Deposited: 5000
SMS: Deposited 5000
Email: Deposited 5000
Audit: Deposit of 5000 recorded
Balance: 5000

1.5 Step-by-Step Program Explanation

Step 1: Declare the event.

public event Action<decimal>? DepositCompleted;

The BankAccount class declares an event that accepts a decimal parameter.

Step 2: Subscribe methods to the event.

account.DepositCompleted += notification.SendSMS;
account.DepositCompleted += notification.SendEmail;
account.DepositCompleted += audit.WriteLog;

Three methods subscribe to the same event.

Each subscribed method must have a signature compatible with Action<decimal>.

Step 3: Execute the deposit method.

account.Deposit(5000);

The deposit method validates the amount and updates the balance.

Step 4: Raise the event.

DepositCompleted?.Invoke(amount);

The ?. operator checks whether the event has subscribed handlers.

If handlers exist, they are invoked.

Step 5: Execute subscribed methods.

account.Deposit(5000)
          |
          v
Validate Deposit Amount
          |
          v
Update Account Balance
          |
          v
Raise DepositCompleted Event
          |
          v
    Invocation List
          |
          +----> SendSMS(5000)
          |
          +----> SendEmail(5000)
          |
          +----> WriteLog(5000)
          |
          v
Deposit Method Completed

By default, these handlers execute synchronously in subscription order.

If one handler throws an unhandled exception, subsequent handlers are not invoked through that normal event invocation.

 

1.6 What Is the Difference Between a Delegate and an Event?

Delegate

A delegate is a type-safe reference to a method or a group of methods.

For example:

public Action<string>? Notify;

External code can assign, invoke, or clear this public delegate field.

service.Notify = SendSMS;

service.Notify += SendEmail;

service.Notify?.Invoke("Hello");

service.Notify = null;

Event

An event uses a delegate type but restricts how external code can interact with it.

public event Action<string>? Notify;

External code can subscribe:

service.Notify += SendSMS;

And unsubscribe:

service.Notify -= SendSMS;

However, this is not allowed outside the class declaring a normal field-like event:

// Compilation error
service.Notify?.Invoke("Hello");

// Compilation error
service.Notify = null;

Only the declaring class can directly raise that event.

Comparison Table

Feature

Delegate

Event

Definition

Type-safe method reference

Notification mechanism built on delegates

Keyword

delegate or built-in delegate types

event

Purpose

Execute referenced methods

Notify subscribers

External invocation

Allowed if delegate is accessible

Not allowed for normal field-like events

External assignment

Allowed for accessible delegate fields

Not allowed

Subscribe using +=

Yes

Yes

Unsubscribe using -=

Yes

Yes

Multicast support

Yes

Yes, with multicast delegate types

Common use

Callbacks, strategies, LINQ

Notifications, UI events, domain events

Key Points – Events

  1. An event is built on a delegate type.

  2. Events implement the Publisher–Subscriber pattern.

  3. += subscribes a handler, and -= unsubscribes it.

  4. Events can notify multiple subscribers.

  5. Normal field-like events can only be raised directly by their declaring class.

  6. Event handlers execute synchronously by default.

  7. Events are useful for notifications, UI interactions, and loosely coupled application components.

  8. Long-lived publishers can retain references to subscriber objects, so unsubscribing may be important to prevent memory leaks.

  9. Events are in-process notifications, not a replacement for durable messaging systems such as Azure Service Bus or RabbitMQ.

 

2. What Are Multicast Delegates in C#?

2.1 Definition

A multicast delegate is a delegate that references more than one method.

When the delegate is invoked, all methods in its invocation list are executed sequentially.

In C#, delegate types derived from System.MulticastDelegate support multicast behavior.

Why Do We Need Multicast Delegates?

Suppose a banking application needs to perform three actions after a transaction:

  • Send SMS.

  • Send email.

  • Write an audit log.

Instead of calling each method separately, we can combine them into a single delegate.

Execution Flow

                  Multicast Delegate
                         |
                         v
                    Notify()
                         |
                         v
                  Invocation List
                         |
             +-----------+-----------+
             |           |           |
             v           v           v
          SendSMS     SendEmail    WriteLog
             |           |           |
             v           v           v
          Executed    Executed    Executed

The diagram shows the logical subscribers. With ordinary invocation, execution is sequential rather than parallel.

 

2.2 Complete Example Program – Multicast Delegate

using System;

public delegate void NotificationHandler(string message);

class Program
{
    static void SendSMS(string message)
    {
        Console.WriteLine($"SMS: {message}");
    }

    static void SendEmail(string message)
    {
        Console.WriteLine($"Email: {message}");
    }

    static void WriteLog(string message)
    {
        Console.WriteLine($"Log: {message}");
    }

    static void Main()
    {
        // Create delegate
        NotificationHandler notify = SendSMS;

        // Add more methods
        notify += SendEmail;
        notify += WriteLog;

        // Invoke multicast delegate
        notify("Transaction Completed");
    }
}

Output

SMS: Transaction Completed
Email: Transaction Completed
Log: Transaction Completed

2.3 Step-by-Step Explanation

Step 1: Declare a delegate.

public delegate void NotificationHandler(string message);

The delegate references methods that accept a string and return void.

Step 2: Assign the first method.

NotificationHandler notify = SendSMS;

The invocation list contains one method.

notify
  |
  v
SendSMS

Step 3: Add additional methods.

notify += SendEmail;
notify += WriteLog;

The invocation list now contains three entries.

notify
  |
  v
+----------------+
| SendSMS        |
+----------------+
| SendEmail      |
+----------------+
| WriteLog       |
+----------------+

Step 4: Invoke the delegate.

notify("Transaction Completed");

The methods execute in the order in which they were added.

notify("Transaction Completed")
               |
               v
       SendSMS(message)
               |
               v
      SendEmail(message)
               |
               v
       WriteLog(message)
               |
               v
          Completed
 

2.4 How to Remove a Method from a Multicast Delegate

We can remove a subscribed method using the -= operator.

Example

using System;

class Program
{
    static void Method1()
    {
        Console.WriteLine("Method 1");
    }

    static void Method2()
    {
        Console.WriteLine("Method 2");
    }

    static void Method3()
    {
        Console.WriteLine("Method 3");
    }

    static void Main()
    {
        Action actions = Method1;

        actions += Method2;
        actions += Method3;

        Console.WriteLine("Before Removing:");

        actions();

        // Remove Method2
        actions -= Method2;

        Console.WriteLine("After Removing:");

        actions();
    }
}

Output

Before Removing:
Method 1
Method 2
Method 3
After Removing:
Method 1
Method 3

Explanation

Initially, the invocation list contains three methods.

Before -= Method2

Method1
   |
   v
Method2
   |
   v
Method3

After removing Method2:

After -= Method2

Method1
   |
   v
Method3

Important: If the same method appears multiple times, -= removes the last matching occurrence from the invocation list.

 

2.5 What Happens When Multicast Delegates Return Values?

This is an important interview question.

A multicast delegate can reference methods that return values.

However, when invoked normally, only the return value from the last executed method is returned to the caller.

Example Program

using System;

class Program
{
    static int Add(int a, int b)
    {
        Console.WriteLine("Add Executed");

        return a + b;
    }

    static int Multiply(int a, int b)
    {
        Console.WriteLine("Multiply Executed");

        return a * b;
    }

    static void Main()
    {
        Func<int, int, int> calculate = Add;

        calculate += Multiply;

        int result = calculate(10, 20);

        Console.WriteLine($"Result: {result}");
    }
}

Output

Add Executed
Multiply Executed
Result: 200

Explanation

Both methods execute.

calculate(10, 20)
        |
        v
    Add(10, 20)
        |
        v
    Returns 30
        |
        v
  Multiply(10, 20)
        |
        v
    Returns 200
        |
        v
  Final Result = 200

The result 30 from Add() is not returned to the caller through the normal combined invocation.

The final result is 200, returned by Multiply().

How to Retrieve Every Return Value

Use GetInvocationList() to execute each delegate individually.

using System;

class Program
{
    static int Add(int a, int b) => a + b;

    static int Multiply(int a, int b) => a * b;

    static void Main()
    {
        Func<int, int, int> calculate = Add;

        calculate += Multiply;

        foreach (Delegate handler in calculate.GetInvocationList())
        {
            var method = (Func<int, int, int>)handler;

            int result = method(10, 20);

            Console.WriteLine(result);
        }
    }
}

Output

30
200

Key Points

  • Multicast delegates can contain methods with return values.

  • All referenced methods execute during normal successful invocation.

  • Only the final method's return value is returned.

  • Use GetInvocationList() when individual results are required.

  • For notification-style multicast delegates, void return types are generally preferable.

 

2.6 What Happens if One Method Throws an Exception?

Consider:

using System;

class Program
{
    static void Method1()
    {
        Console.WriteLine("Method 1");
    }

    static void Method2()
    {
        throw new Exception("Something went wrong");
    }

    static void Method3()
    {
        Console.WriteLine("Method 3");
    }

    static void Main()
    {
        Action actions = Method1;

        actions += Method2;
        actions += Method3;

        try
        {
            actions();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Output

Method 1
Something went wrong

Why Does Method3 Not Execute?

The invocation stops when Method2() throws an exception.

Multicast Delegate
        |
        v
     Method1
        |
        v
     Method2
        |
        v
    Exception
        |
        v
  Invocation Stops
        |
        X
     Method3
   Not Executed

Solution – Handle Each Delegate Separately

foreach (Delegate handler in actions.GetInvocationList())
{
    try
    {
        ((Action)handler)();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
}

This allows subsequent handlers to execute even when an earlier handler fails.

Production consideration: Whether execution should continue after a failure depends on business requirements. For financial transactions, errors should not simply be ignored; the system must preserve consistency and report failures appropriately.

 

3. How Are Events and Multicast Delegates Related?

Events commonly use multicast delegate types internally.

When multiple methods subscribe to an event, they become part of its underlying delegate's invocation list.

Example

public event Action? TransactionCompleted;

Subscribe:

TransactionCompleted += SendSMS;
TransactionCompleted += SendEmail;
TransactionCompleted += WriteLog;

Raise:

TransactionCompleted?.Invoke();

Execution:

                TransactionCompleted
                        |
                        v
                  Event Raised
                        |
                        v
               Multicast Delegate
                        |
                        v
                 Invocation List
                        |
            +-----------+-----------+
            |           |           |
            v           v           v
         SendSMS     SendEmail    WriteLog

Main Difference

A multicast delegate is responsible for holding and invoking multiple method references.

An event provides a controlled subscription and notification mechanism using a delegate.

 

4. Events vs Delegates vs Multicast Delegates

Feature

Delegate

Multicast Delegate

Event

Purpose

Reference methods

Reference multiple methods

Notify subscribers

Multiple methods

Supported

Yes, defining characteristic

Commonly supported

+= and -=

Supported

Supported

Supported

External invocation

Depends on accessibility

Depends on accessibility

Restricted for normal field-like events

Return value

Depends on signature

Last handler's result

Depends on underlying delegate

Typical use

Callback

Multiple callbacks

Publisher–Subscriber notifications

Example

Calculate amount

Send SMS + Email

Deposit completed

Note: A multicast delegate is not a separate C# declaration keyword. Ordinary delegate types generally support multicast behavior.

 

5. Additional Important Interview Questions

Q1. Can an event exist without a delegate?

No. Every C# event has a delegate type.

However, you do not need to declare a custom delegate because built-in delegate types such as Action and EventHandler can be used.

Q2. Can multiple methods subscribe to one event?

Yes. Multiple handlers can subscribe using +=.

Q3. Can we invoke an event from another class?

For a normal field-like event, no. Only the class declaring the event can directly invoke it.

External classes can subscribe and unsubscribe.

Q4. Can delegates and events use lambda expressions?

Yes.

Action<string> notify =
    message => Console.WriteLine(message);

Event subscription:

account.DepositCompleted += amount =>
{
    Console.WriteLine($"Deposited: {amount}");
};

For later unsubscription, retain the same delegate instance or use a named method.

Q5. Are multicast delegates asynchronous?

No. Normal multicast delegate invocation is synchronous and sequential.

Multicast delegates do not automatically create threads or execute handlers in parallel.

Q6. What is the difference between EventHandler and Action for events?

EventHandler follows the standard .NET event pattern.

public event EventHandler? Completed;

It receives the sender and event arguments:

void OnCompleted(object? sender, EventArgs e)
{
    Console.WriteLine("Completed");
}

Action is a more general-purpose delegate.

public event Action<string>? Completed;

For public .NET library APIs, EventHandler or EventHandler<TEventArgs> is generally preferred because it follows established event conventions.

Q7. Are delegates immutable?

Yes. Delegate instances are immutable.

When using:

notify += SendEmail;

C# creates a new combined delegate and assigns it to notify.

The original delegate instance is not modified.

 

6. Final Key Points for Interviews

Events

  1. Events are based on delegates.

  2. Events implement the Publisher–Subscriber pattern.

  3. Subscribers register using +=.

  4. Subscribers unregister using -=.

  5. Normal field-like events can only be raised by their declaring class.

  6. Events can notify multiple subscribers.

  7. Standard .NET events commonly use EventHandler or EventHandler<TEventArgs>.

  8. Events execute synchronously by default.

Multicast Delegates

  1. Multicast delegates reference multiple methods.

  2. Methods are combined using += or Delegate.Combine().

  3. Methods are removed using -= or Delegate.Remove().

  4. Methods execute sequentially in invocation-list order.

  5. If methods return values, normal invocation returns the last method's result.

  6. An unhandled exception stops the normal invocation sequence.

  7. GetInvocationList() returns individual delegates.

  8. Delegate instances are immutable.

 

7. Interview-Ready Answers

Q1. What is an event, and how does it differ from a delegate?

An event is a notification mechanism in C# built on delegates. It follows the Publisher–Subscriber pattern, where a publisher raises an event and subscribers respond to it.

The main difference is that a delegate provides a callable method reference, whereas an event controls access to that delegate. External classes can subscribe and unsubscribe from an event but cannot directly invoke a normal field-like event.

Q2. What are multicast delegates?

A multicast delegate is a delegate that references multiple methods. We can combine methods using the += operator and remove them using -=.

When invoked, all methods execute sequentially in subscription order unless an exception interrupts execution. If the methods return values, the result from the last executed method is returned to the caller.

Easy Way to Remember

Delegate
    |
    +----> References one or more methods

Multicast Delegate
    |
    +----> Method1()
    +----> Method2()
    +----> Method3()

Event
    |
    +----> Publisher raises notification
                  |
                  v
             Subscribers
                  |
          +-------+-------+
          |       |       |
          v       v       v
       Handler1 Handler2 Handler3

Most important distinction: A delegate represents executable behavior, a multicast delegate combines multiple handlers, and an event provides controlled subscription to notifications.

← Back to Articles