Delegate Declaration and Usage
Vaibhav • September 11, 2025
In the previous article, we introduced delegates as a way to treat methods as first-class citizens in C#. Now it’s time to dive deeper and understand how to declare delegate types, instantiate them, and invoke them in real-world scenarios. This article builds on your understanding of methods and parameters, and shows how delegates can make your code more flexible, extensible, and expressive.
We’ll walk through the syntax of delegate declarations, explore how to assign methods to delegates, and discuss how delegates behave under the hood. You’ll also learn how to use delegates with instance methods, static methods, and even anonymous functions - all while keeping your code type-safe and readable.
Declaring a Delegate Type
A delegate type defines the method signature that any assigned method must match. This includes the return type and the parameter list. Declaring a delegate is similar to declaring a method - but instead of writing a body, you’re defining a type.
public delegate void Logger(string message);
This defines a new type called Logger
. Any method that takes a single string
parameter and returns void
can be
assigned to a variable of this type.
Delegate types are reference types. You can pass them around like objects, store them in fields, and invoke them dynamically.
Assigning Methods to Delegates
Once you’ve declared a delegate type, you can create a delegate instance by assigning a method that matches the signature. Let’s look at a simple example:
void ConsoleLogger(string msg)
{
Console.WriteLine("Log: " + msg);
}
Logger log = ConsoleLogger;
log("System started."); // Output: Log: System started.
The method ConsoleLogger
matches the Logger
delegate’s signature, so it can be assigned directly. When you invoke log("System started.")
, it calls ConsoleLogger
with that argument.
Using Delegates with Instance Methods
Delegates can point to both static and instance methods. When pointing to an instance method, the delegate stores both the method and the target object. Here’s an example:
class Printer
{
public void Print(string text)
{
Console.WriteLine("Printing: " + text);
}
}
Printer p = new Printer();
Logger log = p.Print;
log("Hello world"); // Output: Printing: Hello world
The delegate log
stores a reference to the Print
method and the instance p
. When invoked,
it calls p.Print
with the given argument.
Note: Delegates maintain the target object internally. This allows them to invoke instance methods even when the original object is out of scope.
Invoking Delegates
You invoke a delegate just like a method - using parentheses and passing arguments. Behind the scenes, the runtime checks that the delegate is not null and then calls the referenced method.
Logger log = ConsoleLogger;
log("Initialization complete.");
This is equivalent to calling ConsoleLogger("Initialization complete.")
, but
with the added flexibility of indirection.
Always check if a delegate is null before invoking it, especially in event-driven code. Use
the null-conditional operator (?.
) to avoid exceptions.
log?.Invoke("Safe call");
Delegates as Parameters
One of the most powerful uses of delegates is passing them as parameters to other methods. This allows you to inject behavior into a method - a key idea in functional programming and design patterns.
void ProcessData(string data, Logger logger)
{
// Simulate processing
logger("Processing: " + data);
}
ProcessData("Sensor reading", ConsoleLogger);
The method ProcessData
accepts a delegate and uses it to log messages. This
makes the logging behavior customizable - you can pass different loggers depending on context.
Delegates and Return Values
Delegates can also return values. Let’s define a delegate that performs a calculation:
public delegate int Calculator(int x, int y);
int Multiply(int a, int b) => a * b;
Calculator calc = Multiply;
int result = calc(3, 4); // result == 12
The delegate Calculator
returns an int
, and
Multiply
matches the signature. You can now use calc
to perform multiplication.
Combining Delegates
Delegates support multicast - meaning you can combine multiple delegates into one. When invoked, all methods are called in order. This is useful for event handling and notification systems.
void LogToConsole(string msg) => Console.WriteLine("Console: " + msg);
void LogToFile(string msg) => Console.WriteLine("File: " + msg); // Simulated
Logger combined = LogToConsole;
combined += LogToFile;
combined("System update");
// Output:
// Console: System update
// File: System update
The +=
operator adds a method to the invocation list. When combined
is called, both methods run in sequence.
Multicast delegates ignore return values. If your delegate has a return type, only the result of the last method is returned. Use multicast only when return values aren’t needed.
Removing Methods from Delegates
You can remove methods from a delegate using the -=
operator. This is useful
for unsubscribing from events or changing behavior dynamically.
combined -= LogToFile;
combined("After removal");
// Output:
// Console: After removal
Now only LogToConsole
is invoked. The delegate’s invocation list is updated at
runtime.
Delegates and Type Inference
In modern C#, you can use method group conversions to assign methods to delegates without explicitly using new
. This makes the syntax cleaner and more intuitive.
Logger log = ConsoleLogger; // Implicit conversion
Calculator calc = Multiply; // No need for new Calculator(Multiply)
The compiler infers the delegate type from the method signature. This works only when the method matches the delegate exactly.
Delegates vs Interfaces
You might wonder - when should I use a delegate, and when should I use an interface? Delegates are ideal for short-lived, pluggable behavior - like callbacks, filters, or event handlers. Interfaces are better for long-term contracts and polymorphism.
For example, a sorting algorithm might accept a delegate for comparison logic, while a payment system might use an interface to define payment processors.
Use delegates when you need to pass behavior as a parameter. Use interfaces when you need to define a set of related behaviors across multiple classes.
Summary
In this article, we explored how to declare and use delegates in C#. You learned how to define delegate types, assign methods, invoke delegates, and pass them as parameters. We covered both static and instance methods, return values, multicast behavior, and type inference.
Delegates are a powerful tool for decoupling logic and injecting behavior. They enable flexible design patterns and functional programming techniques. In the next article, we’ll explore Multicast Delegates in more detail - including how they work internally and when to use them effectively.