Paramclasses Academy - Blog

Tutorials Details

←
Post 23 of 30
→

What is Delegates and Events in C# ?

What is Delegates and Events in C# ?

October 26, 2026

Introduction

C# is a powerful programming language that supports object-oriented programming, event-driven programming, and reusable code. Two important features that help developers build flexible and interactive applications are delegates and events.

Delegates allow you to treat methods as values that can be passed to other methods, stored in variables, and invoked when required. Events provide a structured way for one object to notify other objects when something happens.

These concepts are commonly used in desktop applications, web applications, game development, notification systems, and asynchronous workflows.

In this tutorial, you will learn what delegates and events are, how they work, when to use them, and how to implement them through practical C# examples.

1. What Is a Delegate in C#?

A delegate is a type that represents references to methods with a specific parameter list and return type.

In simple terms, a delegate acts like a type-safe reference to a method. You can assign a compatible method to a delegate and invoke that method through the delegate.

Why Are Delegates Useful?

Delegates are useful when you want to:

  • Pass a method as an argument to another method.

  • Decide at runtime which method should execute.

  • Implement callbacks.

  • Support event-driven programming.

  • Create flexible and reusable components.

Delegate Syntax

access_modifier delegate return_type DelegateName(parameters);

For example:

public delegate void MessageHandler(string message);

Here:

  • public defines the accessibility of the delegate.

  • delegate declares a delegate type.

  • void specifies that the represented method returns no value.

  • MessageHandler is the delegate name.

  • string message defines the required parameter.

Any method assigned to this delegate must have a compatible signature.

2. Creating and Using Your First Delegate

Let's create a simple delegate that refers to a method displaying a message.

using System;

public delegate void MessageHandler(string message);

class Program
{
    static void DisplayMessage(string message)
    {
        Console.WriteLine(message);
    }

    static void Main()
    {
        MessageHandler handler = DisplayMessage;

        handler("Hello from a C# delegate!");
    }
}

Output:

Hello from a C# delegate!

How Does This Code Work?

  1. MessageHandler declares a delegate type.

  2. DisplayMessage() is a method whose signature matches the delegate.

  3. MessageHandler handler = DisplayMessage; assigns the method to the delegate.

  4. handler(...) invokes the referenced method.

Notice that the method name is assigned without parentheses. Writing DisplayMessage() would call the method immediately rather than assign the method reference.

Modern C# also supports the new MessageHandler(DisplayMessage) syntax, but the shorter form is generally easier to read.

3. Passing a Delegate as a Method Parameter

One of the most useful features of delegates is the ability to pass behavior into another method.

using System;

public delegate int Calculator(int a, int b);

class Program
{
    static int Add(int a, int b)
    {
        return a + b;
    }

    static int Multiply(int a, int b)
    {
        return a * b;
    }

    static void ExecuteOperation(
        int x,
        int y,
        Calculator operation)
    {
        int result = operation(x, y);
        Console.WriteLine($"Result: {result}");
    }

    static void Main()
    {
        ExecuteOperation(10, 5, Add);
        ExecuteOperation(10, 5, Multiply);
    }
}

Output:

Result: 15
Result: 50

The ExecuteOperation() method does not need to know whether it will perform addition or multiplication. The caller supplies the required behavior through a delegate.

This approach makes code more flexible and helps reduce unnecessary duplication.

4. Multicast Delegates in C#

A multicast delegate can reference multiple compatible methods. When invoked, those methods execute in their invocation-list order.

You can combine methods with the += operator and remove them with -=.

using System;

public delegate void Notification();

class Program
{
    static void SendEmail()
    {
        Console.WriteLine("Email notification sent.");
    }

    static void SendSms()
    {
        Console.WriteLine("SMS notification sent.");
    }

    static void Main()
    {
        Notification notify = SendEmail;
        notify += SendSms;

        notify();

        notify -= SendSms;

        Console.WriteLine("After removing SMS:");
        notify();
    }
}

Output:

Email notification sent.
SMS notification sent.
After removing SMS:
Email notification sent.

Important Points

  • += adds a method to the invocation list.

  • -= removes a matching method from the invocation list.

  • If a multicast delegate returns a value, invoking it normally returns the value from the last method executed.

  • If one method throws an exception, later methods in the invocation list may not execute.

For notification workflows, delegates with a void return type are often appropriate.

5. Built-in Delegates: Action, Func, and Predicate

C# provides generic delegate types that reduce the need to declare custom delegates for common scenarios.

Action Delegate

Action represents a method that returns void.

using System;

class Program
{
    static void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }

    static void Main()
    {
        Action<string> display = PrintMessage;

        display("Learning Action in C#");
    }
}

Output:

Learning Action in C#

Use Action when the method performs an operation without returning a value.

Func Delegate

Func represents a method that returns a value. Its final generic type parameter specifies the return type.

using System;

class Program
{
    static int Add(int a, int b)
    {
        return a + b;
    }

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

        Console.WriteLine(calculate(10, 20));
    }
}

Output:

30

In Func<int, int, int>, the first two int types represent the input parameters, and the last int represents the return type.

Predicate Delegate

Predicate<T> represents a method that accepts a value of type T and returns a bool.

using System;

class Program
{
    static bool IsAdult(int age)
    {
        return age >= 18;
    }

    static void Main()
    {
        Predicate<int> checkAge = IsAdult;

        Console.WriteLine(checkAge(20));
        Console.WriteLine(checkAge(15));
    }
}

Output:

True
False

Action vs Func vs Predicate

Delegate Return type Typical use
Action<T> void Performing an operation
Func<T, TResult> TResult Calculating or returning a value
Predicate<T> bool Testing a condition

The generic type parameters determine the inputs and, where applicable, the return type.

6. What Is an Event in C#?

An event is a mechanism that allows an object to notify other objects when something happens.

Events are built on delegates, but they provide controlled notification access. A publisher raises an event, while subscribers register methods to respond to it.

For example, consider an online shopping application:

  • The order service is the publisher.

  • An email notification service is a subscriber.

  • An inventory service is another subscriber.

  • When an order is confirmed, both subscribers can respond.

The publisher does not need to know the internal details of each subscriber.

Key Components of an Event

  • Publisher: The object that declares and raises the event.

  • Subscriber: The object that registers to receive notifications.

  • Event handler: The method executed when the event is raised.

  • Event arguments: Optional data describing what happened.

7. Creating Your First Event in C#

C# commonly uses the built-in EventHandler delegate for events that do not require custom event data.

using System;

class Alarm
{
    public event EventHandler? AlarmTriggered;

    public void Trigger()
    {
        Console.WriteLine("Alarm is triggered.");

        AlarmTriggered?.Invoke(this, EventArgs.Empty);
    }
}

class Program
{
    static void OnAlarmTriggered(object? sender, EventArgs e)
    {
        Console.WriteLine("Notification: Please check the alarm!");
    }

    static void Main()
    {
        Alarm alarm = new Alarm();

        alarm.AlarmTriggered += OnAlarmTriggered;

        alarm.Trigger();
    }
}

Output:

Alarm is triggered.
Notification: Please check the alarm!

This example uses nullable reference type annotations such as EventHandler? and object?. They are appropriate in projects where nullable reference types are enabled.

Understanding the Code

  1. AlarmTriggered declares an event.

  2. OnAlarmTriggered() is the event handler.

  3. += subscribes the handler to the event.

  4. Trigger() raises the event through Invoke().

  5. The registered handler runs when the event is raised.

The ?.Invoke(...) syntax checks whether there are any subscribers before attempting to invoke the event.

A key rule is that external code can subscribe to or unsubscribe from a normal C# event, but it cannot directly raise that event. The declaring type controls when the event is invoked.

8. Creating an Event with Custom Event Data

Sometimes subscribers need more than a simple notification. For example, when an order is placed, subscribers may need the order ID and total amount.

You can create a custom class derived from EventArgs to carry this information.

using System;

public class OrderEventArgs : EventArgs
{
    public int OrderId { get; }
    public decimal TotalAmount { get; }

    public OrderEventArgs(int orderId, decimal totalAmount)
    {
        OrderId = orderId;
        TotalAmount = totalAmount;
    }
}

class OrderService
{
    public event EventHandler<OrderEventArgs>? OrderPlaced;

    public void PlaceOrder(int orderId, decimal totalAmount)
    {
        Console.WriteLine($"Order {orderId} has been placed.");

        OrderPlaced?.Invoke(
            this,
            new OrderEventArgs(orderId, totalAmount));
    }
}

class Program
{
    static void SendOrderConfirmation(
        object? sender,
        OrderEventArgs e)
    {
        Console.WriteLine($"Order ID: {e.OrderId}");
        Console.WriteLine($"Total: {e.TotalAmount:C}");
    }

    static void Main()
    {
        OrderService service = new OrderService();

        service.OrderPlaced += SendOrderConfirmation;

        service.PlaceOrder(101, 2499.50m);
    }
}

Example output:

Order 101 has been placed.
Order ID: 101
Total: ₹2,499.50

The currency symbol and formatting depend on the system's current culture settings, so the exact currency display may vary.

Why Use Custom Event Arguments?

Custom event data allows subscribers to access the information associated with an event without tightly coupling the publisher to the subscribers' implementation details.

You can include properties such as:

  • Order ID

  • Customer ID

  • Transaction amount

  • File name

  • Download progress

  • Processing status

9. Multiple Subscribers for the Same Event

One event can have several subscribers. Each registered handler can respond independently when the publisher raises the event.

using System;

class DownloadManager
{
    public event EventHandler? DownloadCompleted;

    public void CompleteDownload()
    {
        Console.WriteLine("Download completed.");

        DownloadCompleted?.Invoke(this, EventArgs.Empty);
    }
}

class Program
{
    static void ShowMessage(object? sender, EventArgs e)
    {
        Console.WriteLine("File downloaded successfully.");
    }

    static void SaveDownloadRecord(object? sender, EventArgs e)
    {
        Console.WriteLine("Download record saved.");
    }

    static void Main()
    {
        DownloadManager manager = new DownloadManager();

        manager.DownloadCompleted += ShowMessage;
        manager.DownloadCompleted += SaveDownloadRecord;

        manager.CompleteDownload();
    }
}

Output:

Download completed.
File downloaded successfully.
Download record saved.

This pattern is helpful when one action must trigger several independent responses, such as updating the user interface, recording an audit entry, or sending a notification.

10. Unsubscribing from Events

Subscribers should unsubscribe when they no longer need notifications, especially when a long-lived publisher might otherwise keep a subscriber reachable in memory.

Use -= to remove a previously registered handler.

manager.DownloadCompleted -= ShowMessage;

For a complete example:

using System;

class Publisher
{
    public event EventHandler? Updated;

    public void Notify()
    {
        Updated?.Invoke(this, EventArgs.Empty);
    }
}

class Program
{
    static void HandleUpdate(object? sender, EventArgs e)
    {
        Console.WriteLine("Update received.");
    }

    static void Main()
    {
        Publisher publisher = new Publisher();

        publisher.Updated += HandleUpdate;
        publisher.Notify();

        publisher.Updated -= HandleUpdate;
        publisher.Notify();
    }
}

Output:

Update received.

The second Notify() call produces no message because the handler has been unsubscribed.

If you subscribe using a lambda expression and need to unsubscribe later, keep a reference to the delegate or use a named method so that you can remove the same handler.

11. Delegates vs Events in C#

Delegates and events are closely related, but they serve different purposes.

Feature Delegate Event
Main purpose Reference and invoke compatible methods Notify subscribers that something happened
Declaration delegate keyword or built-in delegate types event keyword with a delegate type
Invocation A delegate variable can generally be invoked by code that can access it Normally raised only by the declaring type
Subscription Methods can be combined using += Methods subscribe using +=
Unsubscription Methods can be removed using -= Methods unsubscribe using -=
Typical use Callbacks and passing behavior Notifications and publisher-subscriber patterns

Simple rule: Use a delegate when you need to pass or store a method reference. Use an event when an object needs to notify other objects that an action or state change has occurred.

12. Real-World Applications of Delegates and Events

Delegates and events are useful across many types of software.

Desktop Applications

Buttons, menu items, and other controls raise events when users interact with them. Event handlers respond to those interactions.

E-Commerce Applications

An order service can raise an order-placed event. Separate subscribers can update inventory, create notifications, or record order activity.

Game Development

Events can signal that a player has scored, a level has completed, or a game state has changed.

File Processing

A file-processing service can notify subscribers when a file starts processing, finishes processing, or encounters an error.

Background Operations

Applications can use events to report progress or completion. For asynchronous workflows, tasks and cancellation tokens are also important tools, depending on the problem.

13. Common Mistakes to Avoid

Mistake 1: Using an Incompatible Method Signature

A method must match the delegate's parameter and return types.

public delegate void MessageHandler(string message);

A method that accepts an int instead of a string cannot be assigned directly to this delegate.

Mistake 2: Forgetting to Subscribe

Declaring an event does not automatically register a handler. Use += to subscribe.

Mistake 3: Raising an Event from Outside the Publisher

For a normal field-like event, external code cannot invoke the event directly. Provide a public method on the publisher when external callers need to request an operation that may raise the event.

Mistake 4: Forgetting to Unsubscribe

Long-lived publishers and short-lived subscribers can create unwanted object-lifetime issues if event handlers remain registered unnecessarily.

Mistake 5: Confusing Delegates with Events

Delegates represent callable method references. Events use delegate-based notification with controlled invocation access.

Mistake 6: Ignoring Exceptions in Event Handlers

If one handler throws an exception during a normal multicast event invocation, subsequent handlers may not execute. If every subscriber must be attempted independently, design explicit exception-handling and dispatch behavior rather than assuming the event automatically isolates failures.

14. Best Practices for Using Delegates and Events

  • Use Action, Func, and Predicate<T> when they express the required behavior clearly.

  • Use meaningful delegate and event names.

  • Follow the standard EventHandler or EventHandler<TEventArgs> pattern where appropriate.

  • Keep event handlers focused and avoid unnecessary long-running work.

  • Unsubscribe when the subscriber's lifecycle requires it.

  • Avoid exposing unnecessary ways to raise events from outside the publisher.

  • Use custom event arguments when subscribers need relevant event data.

  • Consider thread safety when events are raised from multiple threads.

  • Do not assume event subscribers execute asynchronously; normal event handlers execute synchronously unless you explicitly implement another dispatch strategy.

15. Practice Exercises

Try these exercises to strengthen your understanding.

Exercise 1: Simple Delegate

Create a delegate named GreetingHandler that accepts a person's name and prints a greeting.

Exercise 2: Calculator Delegate

Create a delegate that supports addition, subtraction, multiplication, and division. Pass different methods to a common execution method.

Exercise 3: Action and Func

Use Action<string> to display a message and Func<int, int, int> to calculate the sum of two numbers.

Exercise 4: Multicast Delegate

Create three methods that display different messages. Combine them into one multicast delegate and invoke it.

Exercise 5: Notification Event

Create a TemperatureSensor class with an event that notifies subscribers when the temperature exceeds a specified threshold.

Exercise 6: Custom Event Arguments

Create a PaymentCompletedEventArgs class containing a transaction ID and payment amount. Raise an event when a simulated payment completes.

Exercise 7: Unsubscribe from an Event

Register two handlers with an event. Remove one handler and verify that only the remaining handler receives the next notification.

16. Quick Knowledge Check

1. What does a delegate represent in C#?

A. A database table
B. A reference to compatible methods
C. A namespace
D. A loop

Correct answer: B

2. Which keyword is used to declare an event?

A. signal
B. notify
C. event
D. delegateevent

Correct answer: C

3. Which operator is commonly used to subscribe to an event?

A. +=
B. ==
C. &&
D. ??

Correct answer: A

4. Which built-in delegate is designed for methods that return a value?

A. Action
B. Func
C. EventHandler only
D. void

Correct answer: B

5. Can external code normally invoke a field-like C# event directly?

A. Yes, from anywhere
B. Only inside a loop
C. No, invocation is controlled by the declaring type
D. Only when the event has no subscribers

Correct answer: C

17. Frequently Asked Questions (FAQs)

What is a delegate in C#?

A delegate is a type-safe reference to methods with compatible signatures. It allows methods to be passed as arguments, stored, and invoked indirectly.

What is an event in C#?

An event is a notification mechanism that allows an object to inform registered subscribers when something happens.

What is the difference between a delegate and an event?

A delegate represents callable method references. An event uses a delegate-based mechanism to notify subscribers while restricting normal external invocation.

What are Action, Func, and Predicate in C#?

They are built-in generic delegate types. Action represents methods returning void, Func represents methods returning a value, and Predicate<T> represents methods returning a Boolean value.

Can a C# event have multiple subscribers?

Yes. Multiple handlers can subscribe to the same event using +=. When the event is raised, the registered handlers are invoked in their invocation-list order.

Why should you unsubscribe from an event?

Unsubscribing prevents a handler from receiving future notifications. It can also help avoid unintended object-lifetime and memory-retention issues when publishers live longer than subscribers.

Are delegates and events difficult for beginners?

They may initially seem unfamiliar, but simple delegate examples, notification events, and publisher-subscriber exercises make the concepts easier to understand.

Conclusion

Delegates and events are essential C# concepts for building flexible, reusable, and event-driven applications. Delegates allow methods to be passed and invoked through type-safe references, while events enable publishers to notify multiple subscribers without depending on their implementation details.

Start by practicing custom delegates, then learn Action, Func, and Predicate<T>. After that, implement events with multiple subscribers, custom event data, and proper unsubscription.

Once these concepts are clear, you will be better prepared to understand callbacks, UI event handling, notifications, and many real-world .NET application patterns.

←
Post 23 of 30
→