Delegates in C#: The Ultimate Beginner-to-Advanced Guide

Unlocking the power of function references, events, and runtime flexibility in C#

Delegates are the core building block of dynamic, event-driven, and functional programming in C#. They allow methods to be treated as first-class objects, stored in variables, passed as parameters, chained together, and invoked at runtime.

This guide goes beyond basic definitions. By the end, you’ll understand:

  • What delegates are and how they work internally
  • Single-cast vs multicast delegates
  • Anonymous methods and lambda expressions
  • Built-in generic delegates (Func, Action, Predicate)
  • Events and the difference between delegates and events
  • Delegate chains, multicast pitfalls, and invocation lists
  • Real-world use cases in LINQ, callbacks, and GUI programming
  • Best practices, patterns, and debugging tips

This is the definitive guide for beginners, intermediates, and even advanced developers who want to master delegates.


🔍 What is a Delegate?

At its core, a delegate is a type-safe function pointer. It defines a signature (parameter types and return type), and any method matching that signature can be assigned to it.

Think of it as a contract:

  • “I accept two integers and return an integer”
  • “I accept a string and return void”
public delegate int MathOperation(int a, int b);

Here, MathOperation can reference any method taking two ints and returning an int.

Why Delegates Exist

  1. Callbacks – Execute a method dynamically later.
  2. Events – Power event-driven programming in UI, servers, and libraries.
  3. Loose coupling – Decouple classes from concrete implementations.
  4. Functional patterns – Pass behavior as an argument.
  5. Dynamic pipelines – Methods can be selected at runtime.

⚡ Single-Cast vs Multicast Delegates

Single-Cast Delegates

A single-cast delegate references exactly one method:

public delegate void Logger(string message);

class Program
{
    static void ConsoleLog(string msg) => Console.WriteLine(msg);

    static void Main()
    {
        Logger log = ConsoleLog;
        log("Hello World!"); // Calls ConsoleLog
    }
}

✅ Key points:

  • Only one method
  • Type-safe: compiler ensures signature match
  • Can reference static or instance methods

Multicast Delegates

A delegate can reference multiple methods in a chain. This is called a multicast delegate:

Logger log = ConsoleLog;
log += msg => System.IO.File.AppendAllText("log.txt", msg + "\n");

log("Logging to console and file!");
  • Use += to add methods, -= to remove them
  • All methods are called in order
  • Exceptions in one method stop execution unless handled

Advanced Tip: You can inspect the invocation list:

foreach (var d in log.GetInvocationList())
{
    Console.WriteLine(d.Method.Name);
}

🧠 Anonymous Methods & Lambda Expressions

Anonymous Methods

Provide inline method bodies without naming them:

Logger log = delegate(string msg) { Console.WriteLine(msg); };
log("Anonymous method!");

Lambda Expressions

The modern standard, concise way to declare inline methods:

Logger log = msg => Console.WriteLine(msg);
log("Lambda expression!");

Why Lambdas Matter:

  • Used extensively in LINQ
  • Simplify event handlers and callbacks
  • Enable functional patterns without verbose code

🛠 Generic Delegates: Func, Action, Predicate

C# provides generic delegates for most scenarios:

DelegateParametersReturnsNotes
Func<T1,…,TReturn>NReturns valueReplace custom delegates
Action<T1,…>NVoidNo return value
Predicate<T>1boolFilter/condition logic

Examples:

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

Action<string> shout = msg => Console.WriteLine(msg.ToUpper());
shout("hello world"); // HELLO WORLD

Predicate<int> isEven = n => n % 2 == 0;
Console.WriteLine(isEven(4)); // True

🌐 Delegates vs Events

Delegates are method references.
Events are encapsulated delegates with restricted invocation.

public delegate void Notify(string message);

class Process
{
    public event Notify OnComplete;

    public void Run()
    {
        Console.WriteLine("Processing...");
        OnComplete?.Invoke("Done!");
    }
}

✅ Notes:

  • Only the declaring class can invoke the event
  • Prevents external misuse
  • Essential for safe public APIs

🧩 Real-World Usage Patterns

Callbacks

public delegate void Callback(string message);

class Worker
{
    public void DoWork(Callback callback)
    {
        Console.WriteLine("Working...");
        callback("Work complete!");
    }
}

Worker w = new Worker();
w.DoWork(msg => Console.WriteLine(msg));

Sorting With Delegates

List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
names.Sort((a,b) => b.Length.CompareTo(a.Length)); // Delegate via lambda
Console.WriteLine(string.Join(", ", names)); // Charlie, Alice, Bob

Event-Driven GUI

button.Click += (s,e) => MessageBox.Show("Button clicked!");

LINQ Filtering

List<int> numbers = new List<int> { 1,2,3,4,5 };
var even = numbers.FindAll(n => n % 2 == 0); // Predicate delegate
Console.WriteLine(string.Join(",", even)); // 2,4

📦 Advanced Delegate Topics

Delegate Chaining

  • Multiple delegates can be chained
  • Return values: Only last method’s return value is used
Func<int,int> compute = x => x+1;
compute += x => x*2; // Last result returned

Exception Handling in Multicast

  • One method throwing an exception prevents downstream calls
  • Use try-catch inside each delegate

Covariance & Contravariance

  • Delegates can reference derived return types (covariance)
  • Accept base types as parameters (contravariance)
Func<object> getObj = () => "string"; // Covariance
Action<string> act = obj => Console.WriteLine(obj); // Contravariance

🧰 Debugging Delegates

  • Use .GetInvocationList() to inspect chained methods
  • Check for null before invoking (?.Invoke)
  • Multicast delegates require careful handling of exceptions
  • Use logging inside delegate methods for tracing

✅ Best Practices

  1. Prefer Func, Action, Predicate over custom delegates
  2. Wrap delegate exposure with events
  3. Check null before invoking (?.Invoke)
  4. Handle exceptions inside multicast delegates
  5. Use lambda expressions for short inline methods
  6. Keep delegate chains readable
  7. Document delegate usage clearly

🧠 Under the Hood: How Delegates Work

  • Delegates are objects storing:
    • Method pointer
    • Target object (for instance methods)
  • Multicast delegates maintain internal invocation lists
  • Invocation is type-checked at runtime
  • Efficient and optimized by the runtime

📊 Section 11 — Summary Table

FeatureSyntaxNotes
Single-castLogger log = ConsoleLog;One method only
Multicastlog += FileLog;Multiple methods
Anonymousdelegate(string msg){...}Inline, no name
Lambdamsg => Console.WriteLine(msg)Concise modern syntax
Func / Action / PredicateFunc<int,int,int> add = ...Generic built-ins
Eventpublic event Notify OnComplete;Safe public exposure
User-definedpublic delegate string Formatter(string input);Semantic clarity

Final Thoughts

Delegates are the cornerstone of dynamic, functional, and event-driven programming in C#.

Once mastered, they enable:

  • Callbacks, events, and LINQ patterns
  • Decoupled, reusable, and flexible code
  • Runtime behavior selection
  • Clean integration with modern C# features

Delegates make C# more than object-oriented — they bring methods to life as first-class citizens, empowering developers to build robust, maintainable, and elegant solutions.