Codectionary / Developer documentation / C#

Delegates

A delegate is a type-safe reference to a method - essentially a variable that can hold a method and be invoked like one. Delegates enable passing behavior around as data, forming the foundation for events, callbacks, and LINQ. C# provides built-in generic delegate types, Func<> and Action<>, that cover the vast majority of use cases without needing to declare a custom delegate type.

Syntax

delegate returnType DelegateName(parameters);

Examples

Declaring and Using a Custom Delegate

Defining a delegate type and assigning a matching method to it.

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

int Add(int a, int b) => a + b;
int Multiply(int a, int b) => a * b;

MathOperation operation = Add;
Console.WriteLine(operation(3, 4));  // 7

operation = Multiply;
Console.WriteLine(operation(3, 4));  // 12

Using Func<> and Action<> Instead

Most of the time, the built-in generic delegates cover what you need without declaring your own type.

Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4));  // 7

Action<string> logMessage = msg => Console.WriteLine($"LOG: {msg}");
logMessage("Application started");

Multicast Delegates

A delegate can reference multiple methods at once, invoking all of them in order when called.

Action<string> notifyEmail = msg => Console.WriteLine($"Email: {msg}");
Action<string> notifySms = msg => Console.WriteLine($"SMS: {msg}");

Action<string> notifyAll = notifyEmail + notifySms;  // combine with +
notifyAll("Order shipped");
// Email: Order shipped
// SMS: Order shipped

Passing a Delegate as a Method Parameter

A very common pattern - accepting behavior as a parameter, similar to how LINQ methods accept lambdas.

void ProcessNumbers(List<int> numbers, Action<int> action)
{
    foreach (int n in numbers)
    {
        action(n);
    }
}

List<int> nums = new List<int> { 1, 2, 3 };
ProcessNumbers(nums, n => Console.WriteLine(n * n));  // 1, 4, 9

Best practices

  • Use the built-in Func<>, Action<>, and Predicate<> generic delegates instead of declaring a custom delegate type, unless you need a very specific, named signature
  • Use delegates to pass behavior into a method (like a callback or strategy), rather than duplicating similar methods with slightly different logic
  • Be aware that combining delegates with + creates a multicast delegate that invokes every referenced method in order - useful for notifications, less so for methods with return values (only the last one's result is kept)
  • Prefer events (built on top of delegates) instead of a raw public delegate field when other code should be able to subscribe to, but not directly invoke or reassign, a notification

At a glance

Purpose
Applications on the .NET platform
File extension
.cs
Runs in
.NET runtime
Usually used with
.NET SDK and libraries

Specifications & further reading

Related C# documentation

Events
An event is a special kind of delegate field that provides a publish-subscribe pattern: the declaring class can raise (invoke) the event, but external code can only subscribe (+=) or unsubscribe (-=) - it cannot invoke the event directly or overwrite existing subscribers. This makes events the standard, safe way to notify other parts of a program that something has happened, like a button being clicked or a value changing.
Extension Methods
Extension methods let you add new methods to an existing type - including types you don't own, like built-in .NET types or third-party classes - without modifying its source code or creating a subclass. They're defined as static methods in a static class, with the first parameter prefixed with this to indicate which type they extend. LINQ's Where(), Select(), and friends are themselves implemented as extension methods on IEnumerable<T>.
Nullable Reference Types & Null-Coalescing
Since C# 8, nullable reference types let the compiler track and warn about potential null reference issues at compile time, even for reference types like string and custom classes (which were always implicitly nullable before). A type like string means 'never null' under this feature, while string? explicitly allows null. The null-coalescing operator (??) and null-conditional operator (?.) provide concise syntax for safely working with potentially-null values.
Pattern Matching
Pattern matching, expanded significantly since C# 7, lets you test a value against a shape or condition using is, switch expressions, and more, often extracting matched data in the same step. It covers type patterns (checking and casting in one step), property patterns (matching on an object's properties), and relational patterns (matching numeric ranges), making conditional logic more expressive and less error-prone than manual casting.