What are delegates in C#?

C# Delegates & Events Intermediate 1–2 Yrs2–3 Yrs3–5 Yrs Conceptual 2 min read

Quick Answer

A delegate is a type-safe reference to a method — effectively a function pointer with a signature. You can store methods in delegate variables, pass them as arguments, and invoke them later. Func<>, Action<> and Predicate<> are built-in generic delegates; events are built on delegates; LINQ and callbacks use them everywhere.

A delegate is a type-safe pointer to a method — it has a signature, and you can put any matching method into it, pass it around, and call it later. In practice I almost never declare my own delegate type; I use the built-in ones — Action for something returning void, Func when it returns a value, Predicate for a bool test. Delegates are multicast, so you can chain handlers with +=. Events are just a locked-down delegate where outside code can subscribe and unsubscribe but can't invoke or overwrite it. LINQ is all delegates under the hood — the lambda you pass to Where is a Func.

Declaring delegate int Transformer(int x); creates a type whose instances hold one or more methods matching int (int). Modern code rarely declares custom delegate types — it uses:

  • Action / Action<T,...> — returns void.
  • Func<T,...,TResult> — returns a value (last type parameter).
  • Predicate<T> — Func<T,bool> by convention.

Key facts:

  • Delegates are multicast — += chains methods; invoking calls them in order (for Func, you get the last return value).
  • Lambdas and method groups are the usual way to create them; a lambda that uses outer variables creates a closure.
  • Events are a restricted delegate wrapper: outside code can only +=/-=, not invoke or reassign.
  • Common uses: LINQ predicates/projections, callbacks, strategy pattern, event handlers, IProgress<T>.

C# — Func, Action, closures
Func<int, int> square = x => x * x;
Action<string> log = msg => Console.WriteLine(msg);

int factor = 3;
Func<int, int> scale = x => x * factor;   // closure over 'factor'

var result = new[] { 1, 2, 3 }.Select(square).Sum();  // 14

// passing behaviour as a parameter (strategy)
void Retry(int times, Action work) { /* ... */ }