Delegates in C#

Delegates in C# are type-safe function references. They allow you to store a reference to a method inside a variable, pass that reference to another method, and invoke the referenced method later. This makes delegates one of the core building blocks behind callbacks, events, LINQ, asynchronous patterns, and flexible application design.

If a class should accept behavior from another part of the program instead of hardcoding that behavior internally, delegates are often part of the solution. They let code say, “give me a method matching this signature, and I will call it when needed.”

Although delegates may look advanced at first, the concept is direct. A delegate defines the shape of a method, and then any matching method can be assigned to it. Once that idea is clear, events, lambda expressions, and many .NET APIs become much easier to understand.


What Is a Delegate in C#?

A delegate is a reference type that can hold the address of a method with a compatible signature. The signature includes the return type and the parameter list.

public delegate void MessageDelegate(string message);

This declaration creates a delegate type named MessageDelegate. It can reference any method that returns void and accepts one string parameter.

Why Delegates Are Important

Delegates make code more flexible because methods can be passed around like data. Instead of a method deciding every step itself, it can accept another method as an argument and call that method when needed. This supports callbacks, strategy-style behavior, plug-in patterns, and event-driven programming.

Many modern C# features are built on this foundation. Lambda expressions are usually assigned to delegates. Events are based on delegates. Generic delegates such as Action, Func, and Predicate appear across the .NET ecosystem.

Delegate Syntax in C#

accessModifier delegate returnType DelegateName(parameterList);

A delegate declaration looks similar to a method declaration, but it ends with a semicolon because it defines a type, not a method body.

Creating and Using a Delegate

To use a delegate, first define the delegate type, then create a method with a matching signature, then assign that method to a delegate variable.

public delegate void DisplayDelegate(string text);

class Program
{
    static void ShowMessage(string text)
    {
        Console.WriteLine(text);
    }

    static void Main()
    {
        DisplayDelegate display = ShowMessage;
        display("Hello from delegate");
    }
}

When display is invoked, the method ShowMessage runs. The delegate variable behaves like a strongly typed method reference.

Delegate Signature Matching

A method can be assigned to a delegate only if the return type and parameter types match the delegate signature. If they do not match, the compiler rejects the assignment.

public delegate int MathDelegate(int x, int y);

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

MathDelegate operation = Add;

Here, Add matches the delegate because both use two integer parameters and return an integer result.

Invoking a Delegate

A delegate can be invoked using method-call syntax, just as if it were a method.

int result = operation(10, 20);
Console.WriteLine(result);

You can also call Invoke() explicitly, but normal call syntax is more common.

int result = operation.Invoke(10, 20);

Passing Delegates to Methods

One of the biggest benefits of delegates is that they can be passed to other methods. This allows one method to accept custom behavior from the caller.

public delegate int OperationDelegate(int x, int y);

static int Execute(int a, int b, OperationDelegate operation)
{
    return operation(a, b);
}

static int Multiply(int x, int y)
{
    return x * y;
}

int answer = Execute(4, 5, Multiply);

This pattern is common in reusable utility code, workflow engines, and logic that should remain open to extension without being rewritten.

Multicast Delegates in C#

C# delegates can hold references to more than one method at the same time. Such delegates are called multicast delegates. When invoked, the methods run in the order they were added.

public delegate void NotifyDelegate();

static void Email() => Console.WriteLine("Email sent");
static void Sms() => Console.WriteLine("SMS sent");

NotifyDelegate notify = Email;
notify += Sms;
notify();

This is very important because events in C# rely on multicast delegate behavior to notify multiple subscribers.

Removing Methods from a Delegate

You can remove a method from a multicast delegate by using the -= operator.

notify -= Sms;

After removal, the delegate invokes only the methods that remain in its invocation list.

Delegates with Return Values

Delegates can return values as long as the signature defines a return type. However, with multicast delegates, only the result of the last invoked method is returned when a return value is expected. That is one reason multicast delegates are most often used with void methods.

public delegate double DiscountDelegate(double amount);

static double ApplyDiscount(double amount)
{
    return amount * 0.9;
}

Built-In Generic Delegates

C# provides built-in generic delegate types so that you do not always need to declare your own custom delegate.

DelegatePurpose
ActionRepresents a method that returns void
FuncRepresents a method that returns a value
PredicateRepresents a method that returns bool for a condition
Action<string> logger = Console.WriteLine;
Func<int, int, int> add = (x, y) => x + y;
Predicate<int> isEven = number => number % 2 == 0;

These built-in delegates are heavily used across modern C# codebases, especially with LINQ and callback-driven APIs.

Delegates vs Interfaces

Delegates and interfaces can both help inject behavior, but they are not the same tool. A delegate is ideal when you need to represent one method or one callback shape. An interface is better when you need a richer contract with multiple methods or properties.

PointDelegateInterface
Main purposeRepresents a method referenceRepresents an object contract
Behavior sizeUsually one operationCan define multiple operations
Best use caseCallbacks and handlersService abstractions and richer behavior

Choosing between them depends on how much behavior needs to be expressed and how strongly the design should model an object-level contract.

Delegates in Real Applications

Delegates are used in button click handlers, timers, sorting logic, async callbacks, filtering operations, retry strategies, middleware pipelines, and event subscriptions. Even if you do not declare custom delegates often, you use delegate-based APIs regularly in modern .NET code.

That is why understanding delegates pays off quickly. It explains how behavior can be passed between methods in a clean, type-safe way.

Common Mistakes with Delegates

  • Trying to assign a method with the wrong parameter list or return type.
  • Forgetting that multicast delegates call every method in the invocation list.
  • Using a custom delegate when Action or Func would be simpler.
  • Ignoring null checks before invoking optional delegates in some designs.
  • Using delegates where an interface contract would express the design more clearly.

Most delegate mistakes come from misunderstanding the shape of the signature or from choosing the wrong abstraction for the job.

Best Practices for Delegates in C#

  • Use delegates when one callback-like operation must be passed around.
  • Prefer Action, Func, and Predicate when a custom delegate adds no extra clarity.
  • Use clear names when declaring custom delegate types for domain-specific behavior.
  • Be careful with multicast delegates that return values.
  • Choose interfaces instead of delegates when a multi-method contract is required.

Delegates Interview Points

For interviews, remember that a delegate is a type-safe reference to a method, the method signature must match, delegates can be passed to methods, and multicast delegates can reference multiple methods. You should also know the relationship between delegates, events, and lambda expressions.

Another useful interview point is explaining when to use Action, Func, or a custom delegate, and when an interface would be a better design choice.

FAQs on Delegates in C#

What is a delegate in C#?

A delegate is a type-safe method reference that can store, pass, and invoke methods whose signatures match the delegate definition.

What is a multicast delegate?

A multicast delegate stores references to multiple methods. When invoked, it runs each method in its invocation list in sequence.

What is the difference between Action and Func?

Action represents methods that return void, while Func represents methods that return a value.

When should I use a delegate in C#?

Use a delegate when behavior should be passed as a callback or handler and the design naturally centers on one method-shaped operation.

Anonymous Methods and Delegates

Before lambda expressions became the most common syntax, C# also supported anonymous methods with the delegate keyword. They still help explain what a delegate really is because they show a method body being assigned directly to a delegate variable.

Action<string> printer = delegate(string value)
{
    Console.WriteLine(value);
};

Lambda expressions are usually preferred today because they are shorter, but anonymous methods are part of the delegate story and still appear in older codebases.

Delegates as Callback Mechanisms

A delegate is often used as a callback. One method starts a process, and when some work is complete, the delegate is invoked to notify the caller or run the next step. This pattern appears in UI programming, background work, retry logic, and pipeline-style processing.

The value of this design is decoupling. The method performing the work does not need to know all the details of what should happen next. It only needs a compatible delegate.

Null Safety When Invoking Delegates

If a delegate variable is optional and may not reference any method yet, invoking it directly can cause a NullReferenceException. In those situations, it is common to check for null or use safe invocation patterns before calling the delegate.

Action? onComplete = null;

onComplete?.Invoke();

This matters especially in event-like or callback-heavy code where subscribers may or may not be attached at runtime.

When a Custom Delegate Is Worth Keeping

Built-in delegates such as Action and Func are often enough, but a custom delegate can still be valuable when the operation is domain-specific and a clear name improves readability. A name like PaymentApprovedHandler can communicate intent better than a raw generic delegate type in some APIs.