C# Delegates: Syntax, Types, and Real-World Use Cases

C-Delegate-feature.jpg

Delegates are one of the most powerful features in C#. They allow you to treat methods as objects, which means you can pass them as parameters, store them in variables, and call them dynamically at runtime. In simple terms, a delegate acts like a pointer or reference to a method, making your program more flexible and modular.

Delegates are especially useful in event-driven programming, callbacks, and scenarios where one method needs to notify or trigger another. They also form the foundation for events, lambda expressions, and asynchronous programming in C#. In this blog, we will explore what a C# delegate is, how it works, its syntax, built-in types, real-world use cases, and best practices to use them effectively.

What is a C# Delegate?

A delegate in C# is a data type that holds a reference to a method. It is similar to the concept of pointers in C/C++. It simply points to or contains the method so that you can pass it as a parameter. This also allows you to store the method as a variable and also call it dynamically at runtime.

C# Delegate helps you when you want to design a flexible program that has room for improvement and expansion in the future. For example, if you are developing an event-driven application or a callback system, one method needs to notify the other so that execution may happen.

Basically, a delegate in C# is a signature or pseudo-name that helps tell the compiler what method will be executed at that point in the code. 

Syntax of C# Delegate

The basic syntax of C# Delegate is similar to a method declaration. The only difference is that the declaration starts with the delegate keyword. 

It specifies the return type and parameter list and hence defines the method signature. The method that is assigned to this C# delegate must match the assigned return type and parameter list. The general syntax of a delegate in C# is as follows:

General Syntax

delegate <return_type> <delegate_name>(<parameter_list>);

You can also specify the access modifier just before the delegate keyword when declaring. 

public delegate void GreetDelegate(string name);

Declaring and Instantiating Delegates in C#

The steps to use C# delegates are quite simple and easy to interpret. It is simple and involves three basic steps.

  1. Declaring a Delegate
  2. Instantiating or Creating a Delegate
  3. Invoking or calling a Delegate

Let us now look at an example that will help you understand declaring and instantiating delegates in C# completely.

Example:

using System;

public class Program
{
// Step 1: Declare a delegate
public delegate void GreetDelegate(string name);

// Step 2: Define a method matching the delegate signature
public static void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}

public static void Main()
{
// Step 3: Instantiate the delegate
GreetDelegate greetDelegate = Greet;

// Step 4: Invoke the delegate
greetDelegate("Garima");
}
}

Output:

Declaring and Instantiating Delegates in C#

Explanation: In the example above, we have declared a delegate type by the name GreetDelegate that has the return type void and accepts a parameter. Then we define a Greet method that matches this method signature. When we called GreetDelegate, the Greet function was called automatically.

Type Safety in C# Delegate

Type safety is one of the primary advantages of C# delegates. This means that a delegate can only reference methods that match the defined signature, both in return type and the number of parameters it accepts. This type of safety ensures that the program is reliable as well as predictable. This happens because if a method does not match the signature of the delegate, the compiler throws an error before the program runs.
This is also the reason why C# delegates are safer than traditional function pointers that are used in languages like C or C++. These pointers usually lead to runtime crashes if misused.

For example, consider the method we used in the example above. The Greet() method exactly matched the method signature of GreetDelegate(), which is why the function ran without any error. 

If we had defined the Greet() function something like this:

public static void Greet()
{
    Console.WriteLine("Hello!");
}

Here, Greet() doesn’t have the required string parameter, so the compiler would prevent it from being assigned to the delegate.

Code:

using System;
public class Program

{
    public delegate void GreetDelegate(string name);
    public static void Greet()
    {
        Console.WriteLine("Hello!");
    }

    public static void Main()

    {
        GreetDelegate greetDelegate = Greet;
        greetDelegate("Garima"); 
    }
}

Output:

Type Safety in C# Delegate

Types of C# Built-in Delegates 

Till now, we have seen how we can define a custom delegate in C#,  but there are a few built-in delegates as well. The three built-in C# delegates are Action, Func, and Predicate, and they have set functionalit, making your code simple and more readable. 

These are widely used in modern C# development when working with LINQ, event handling, and lambda expressions. Let us look at them one by one.

1. Action Delegate

An action delegate returns void and can take zero or more input parameters. To declare an action delegate, you follow this syntax:

Action<param1, param2, ...> delegateName = methodName;

Example:

using System;
public class Program

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

    public static void Main()
    {

        // Action delegate pointing to a method that returns void
        Action<string> actionDelegate = PrintMessage;
        actionDelegate("Hello from Action delegate!");
    }
}

Output: 

Action Delegate

Explanation: Here, the function simply performed the print action without returning anything.

2. Func Delegate

The Func delegate is used with a method that returns a value. It can take zero or more input parameters. The last type parameter defines the return type of the method.

Func<param1, param2, ..., returnType> delegateName = methodName;

Example:

using System;
public class Program

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

    public static void Main()
    {
        // Func delegate pointing to a method that returns an int
        Func<int, int, int> addDelegate = Add;
        int result = addDelegate(5, 7);
        Console.WriteLine($"Sum = {result}");
    }
}

Output:

Function Delegate

Explanation: In this code, the function Func actually returned an integer value that was stored in the result variable and later printed. You should use this when you want to return a value after the execution is completed.

3. Predicate Delegate

Predicate Delegates are special C# delegate methods that take one input and return a Boolean value. These are especially used with searching and filtering operations in collections.

Predicate<T> delegateName = methodName;

Example:

using System;
public class Program

{
    public static bool IsEven(int number)
    {
        return number % 2 == 0;
    }

    public static void Main()
    {
        Predicate<int> isEvenPredicate = IsEven;
        Console.WriteLine(isEvenPredicate(10));
        Console.WriteLine(isEvenPredicate(7)); 
    }
}

Output:

Function Delegate

Explanation: Here, as stated earlier, the IsEven function returned True for 10 and False for 7.

Multicast Delegates & Delegate Chaining in C#

A multicast delegate in C# is a delegate that can reference more than one method at the same time. When the delegate is called, all the methods that are tied to that delegate are executed in the order they were added. These are useful when you want to trigger multiple operations with a single call, for example, sending notifications, logging activities, or updating multiple UI components simultaneously. The only requirement is that all the methods must share the signature, or else it will be useless. 

Note that in Multicast C# Delegate, if one of the methods in a multicast delegate throws an exception, the remaining methods will not be executed.

Add a Method to the Chain

To combine multiple methods, we use the += operator and then remove them using the -= operator. Let us see them with an example,

Example:

using System;
public class Program

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

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

    public static void Main()

    {
        NotifyDelegate notify = SendEmail;
        notify += SendSMS;
        notify("Your report is ready!");
    }
}

Output:

Add a Method to the Chain

Explanation: We have two functions, SendEmail() and SendSMS(), with the same method signatures. Now, the notify C# delegate first points to the SendEmail and then the SendSMS methods. When the notify is called, both of these methods get called in the same order they were assigned.

Get 100% Hike!

Master Most in Demand Skills Now!

Remove a Method From the Chain

To delete a method from the chain, we use the -= operator. Below, we have used the same example from above to make it clear. 

Example:

using System;
public class Program

{
    public delegate void NotifyDelegate(string message);
    public static void SendEmail(string message)

    {
        Console.WriteLine($"Email sent: {message}");
    }

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

    public static void Main()
    {
        NotifyDelegate notify = SendEmail;
        notify += SendSMS;
        Console.WriteLine("=== Before Removing Method ===");
        notify("Reminder: Meeting at 10 AM!");
        notify -= SendEmail;
        Console.WriteLine("\n=== After Removing SendEmail ===");
        notify("Reminder: Meeting at 10 AM!");
    }
}

Output: 

Remove a Method From the Chain

Explanation: Before, Notify was delegated to two methods, SendEmail and SendSMS. Later, using the -= operator, we removed the SendEmail Method, and now only one method gets invoked when calling the Notify C# delegate.

Return Values in Multicast Delegates

Even though a multicast delegate can call multiple methods, it can only return the result of the last invoked method in the list. This is why developers use multicast delegates mostly with methods that return void.

Example:

using System;
public class Program

{
    public delegate int NumberDelegate();
    public static int Method1()
    {
        Console.WriteLine("Method1 called");
        return 10;
    }

    public static int Method2()
    {
        Console.WriteLine("Method2 called");
        return 20;
    }

    public static void Main()
    {
        NumberDelegate del = Method1;
        del += Method2;
        int result = del();
        Console.WriteLine($"Returned value: {result}");
    }
}

Output:

Return Values in Multicast Delegates

Explanation: Both Method1 and Method2 are chained to the NumberDelegate(). When it is invoked(called), both methods get executed. But, only the return value of the last method is actually returned, which is 20.

Delegates and Events in C#

C# Delegates and events are very closely related. Events are built on top of delegates and provide a layer of control over when a delegate gets invoked. To put it simply, a delegate is a kind of function pointer that can call one or more methods. On the other hand, an event is a safe wrapper around a delegate. This means that only the class that defines the event can raise it, while other classes can only subscribe (+=) or unsubscribe (-=) from it. Let us understand with an example how an event or delegate works together.

Example:

This example depicts a Subscriber and Publisher Model.

using System;
public class NotificationService

{

    public delegate void NotifyDelegate(string message);
    public event NotifyDelegate Notify;
    public void SendNotification(string message)

    {
        Console.WriteLine("Preparing to send notification...");
        Notify?.Invoke(message);  // Safe invocation
    }
}

public class Subscriber
{
    public void OnNotify(string message)
    {
        Console.WriteLine($"Notification received: {message}");
    }
}

public class Program
{
    public static void Main()
    {
        NotificationService service = new NotificationService();
        Subscriber user = new Subscriber();
        service.Notify += user.OnNotify;
        service.SendNotification("New course launched on Intellipaat!");
    }
}

Output:

Delegates and Events in C#

Explanation: Notify is an event of that delegate type that can only be invoked from inside the NotificationService class. Also, OnNotify in the Subscriber class handles the event of a user subscribing. 

This pattern is actually used a lot in GUI applications, asynchronous programming, and real-time notifications.

Anonymous Methods and Lambda Expressions with C# Delegates

In programming languages, you must be aware of anonymous methods and lambda expressions. C# delegates allow you to assign these methods directly to a delegate without having to define a separate method.
Anonymous and Lambda Function already make your code simple, readable, and cleaner. Once paired with C# delegates, it reduces the code even more. Let us see how both of these methods can be used with a C# delegate.

Anonymous Methods

An anonymous method is a method without an official name, and with a C# delegate, it allows you to define the body of the method inline.

Example: Using an Anonymous Method

using System;
public class Program

{

    public delegate void GreetDelegate(string name);
    public static void Main()

    {
        // Step 2: Assign an anonymous method to the delegate
        GreetDelegate greet = delegate(string name)

        {
            Console.WriteLine($"Hello, {name}! Welcome to C# Delegates.");
        };

        // Step 3: Invoke the delegate
        greet("Garima");
    }
}

Output:

Anonymous Functions in C# Delegate

Explanation: The delegate(string name) keyword helps you define a nameless method. As you can see, here you didn’t need to write a separate Greet() method as the logic is written directly. This is useful when a delegate is used only once, like in event handlers.

Lambda Expressions

A lambda expression is a different method of expression when compared with anonymous methods, using the => (lambda) operator. It is heavily utilized and is the foundation of LINQ and functional-style programming in C#. In less technical terms, when a programmer uses modern practices to write C# code, they typically use Lambda Expressions, not Anonymous Methods.

Example:

using System;
public class Program

{
    public delegate int SquareDelegate(int num);
    public static void Main()

    {
        SquareDelegate square = num => num * num;
        Console.WriteLine($"Square of 5: {square(5)}");
    }
}

Output: 

Lambda Expressions in C# Delegate

Real-world Use Cases of C# Delegate

C# Delegate is a major concept used in developing event-driven, modular, and flexible applications is C#. Delegates allow developers to pass methods as parameters, trigger events at runtime, and also separate responsibilities between components. Below, we have listed some real-world use cases of C# delegates.

  • Event Handling in GUI Applications: C# delegates are at the core of event handling in frameworks like Windows Forms, WPF, and Xamarin. They make it easy to connect user actions (like button clicks or mouse movements) to event handler methods. 
  • Asynchronous Programming and Callbacks: In asynchronous programs, delegates define callback methods that are executed once a background operation finishes. This allows long-running tasks (like file downloads or API calls) to complete without blocking the main thread.
  • LINQ and Functional Programming: Built-in C# Delegates help in LINQ and functional-style programming in C#. This way, developers pass logic as parameters for filtering, mapping, or transforming collections, which leads to cleaner code.
  • Notification and Event-driven Systems: Through Delegates, publisher-subscriber patterns can be used where multiple components can respond to a single event. For example, a notification service may trigger SMS, email, and push alerts altogether when a message is published.
  • Plugin and Extensibility Frameworks: C# Delegates are used to register plugins or extension points in larger systems. This way, developers can inject new functionality (like logging, validation, or monitoring) without having to modify existing code.
  • Real-time Event Broadcasting: C# Delegates are also widely used in real-time systems such as dashboards, IoT applications, or financial tickers to broadcast updates to multiple listeners.

Best Practices and Common Mistakes of C# Delegate

C# Delegate is very useful when used correctly. To avoid any errors or memory leaks, we have listed down the common mistakes and best practices when using delegates in C#. These best practices will ensure that you write clean and efficient code that is error-free as well. 

Best Practices

  1. Use Built-in Generic Delegates: Always prefer using built-in C# delegates such as Action, Func, and Predicate instead of defining your own custom ones. This maintains consistency with modern C# standards.
  2. Use the Null-conditional Operator for Safe Invocation: Always make sure that a delegate instance has subscribers or is assigned to at least one method before calling it. Using the null-conditional operator (?.Invoke) prevents runtime errors
  3. Unsubscribe When No Longer Needed: In event-based applications, always unsubscribe delegates when they are no longer required.  This avoids memory leaks and unnecessary execution of event handlers.

Common Mistakes

  1. Invoking a Null Delegate: Calling a delegate without checking if it has been assigned to a method can result in a NullReferenceException. Make sure you always verify the delegate before invocation to ensure safe execution.
  2. Overusing Custom Delegate Types: Beginners usually make a habit of defining a new delegate type for every use case. This leads to redundant code and also reduces readability. Most cases can be handled easily using the predefined delegate types provided by default in C#.
  3. Forgetting to Unsubscribe Events: A common issue in applications is forgetting to detach event handlers. This can cause memory leaks and unexpected method calls, as the delegate continues to hold a reference to the target object.

Conclusion

Using C# delegates enhances the flexibility, reusability, and maintainability of your code. Delegates allow methods to be passed around like variables, which simplifies event handling, callbacks, and asynchronous programming. Understanding how to use delegates enables you to write cleaner, more efficient code that adheres to the principles of modular and event-driven programming. Build your code around type safety and proper event handling, regardless of whether you are using built-in delegates (Action, Func, Predicate) or are implementing custom delegates. There are common pitfalls that can occur if you are not careful.

C# Delegate With Examples – FAQs

Q1. What is the difference between delegates and interfaces in C#?

A delegate defines a reference to a single method or group of methods, focusing on behavior invocation. In contrast, an interface defines a contract that classes must implement. Delegates are best suited for callbacks and event handling, while interfaces are ideal for defining reusable object behaviors.

Q2. Can a delegate be declared inside a class or must it always be declared globally?

Delegates can be declared both inside and outside a class. When declared inside a class, they become members of that class and follow its access modifiers. Global declarations are useful when the delegate is shared across multiple classes or modules.

Q3. What happens if a delegate is invoked asynchronously?

Delegates can be executed asynchronously using methods like BeginInvoke() and EndInvoke(). This allows background execution of methods without blocking the main thread. However, these legacy asynchronous patterns are largely replaced by the async/await pattern in modern C#.

Q4. How do delegates differ from function pointers in C and C++?

Although both point to methods, delegates are type-safe and secure, ensuring method signatures match at compile time. Function pointers in C and C++ lack type safety and can lead to undefined behavior. Additionally, delegates support multicast chaining and integration with events, making them more powerful and object-oriented.

Q5. Can delegates capture variables from their enclosing scope?

Yes. When you assign a lambda expression or anonymous method to a delegate, it can capture local variables from the enclosing scope. This feature is known as a closure. It allows the delegate to use variables defined outside its body, even after those variables go out of scope.

About the Author

Technical Content Writer

Garima Hansa is an emerging Data Analyst and Machine Learning enthusiast with hands-on experience through academic and independent projects. She specializes in Python, SQL, data visualization, statistical analysis, and machine learning techniques. Known for building efficient, well-documented solutions and translating complex data insights into actionable recommendations, Garima contributes meaningful value to research, analytics, and developer communities.

Full Stack Developer Course Banner