Skip to content
Delegates and Events
step 1/6

Reading — step 1 of 6

Learn

~2 min readModern C#

Delegates are typed function pointers — they hold references to methods. Events are a publisher-subscriber pattern built on delegates. Together they're how C# does callbacks, event handlers, and pluggable behavior.

Delegates

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

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

BinaryOp op = Add;
Console.WriteLine(op(3, 4));        // 7

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

A delegate is a type. Variables of that type hold method references with matching signatures.

Built-in generic delegates

In modern C# you rarely declare custom delegates — the standard library provides Action<T>, Func<T, TResult>, and Predicate<T>:

Func<int, int, int> add = (a, b) => a + b;
Action<string> log = msg => Console.WriteLine($"[log] {msg}");
Predicate<int> isEven = n => n % 2 == 0;

add(3, 4);          // 7
log("hello");       // [log] hello
isEven(4);          // true
  • Action<...> — takes args, returns nothing
  • Func<..., TResult> — takes args, returns TResult
  • Predicate<T> — takes T, returns bool (essentially Func<T, bool>)

Multicast delegates

Delegates can hold multiple methods. Invoking calls them in order:

Action notify = () => Console.WriteLine("first");
notify += () => Console.WriteLine("second");
notify += () => Console.WriteLine("third");
notify();   // prints first, second, third

notify -= notify;   // remove the most recent (rarely used in practice)

Events

Events are restricted delegates — only the declaring class can invoke them. Subscribers can += and -= but can't trigger:

public class Button
{
    public event Action? Clicked;       // event keyword

    public void Click()
    {
        Clicked?.Invoke();   // null check + raise
    }
}

var btn = new Button();
btn.Clicked += () => Console.WriteLine("clicked!");
btn.Clicked += () => Console.WriteLine("also clicked!");
btn.Click();   // both subscribers fire

Without event, callers could overwrite or invoke the delegate directly — unsafe. The event keyword enforces the publisher-only-invokes contract.

EventHandler — the conventional shape

Framework events use EventHandler<TEventArgs>:

public class Button
{
    public event EventHandler<ClickEventArgs>? Clicked;

    public void Click(int x, int y)
    {
        Clicked?.Invoke(this, new ClickEventArgs(x, y));
    }
}

public class ClickEventArgs : EventArgs
{
    public int X { get; }
    public int Y { get; }
    public ClickEventArgs(int x, int y) { X = x; Y = y; }
}

The (sender, eventArgs) shape is the convention everywhere in .NET — UI frameworks, ASP.NET, etc.

Common mistakes

  • Forgetting to unsubscribe — long-lived publishers keep subscribers alive (memory leak).
  • Calling Clicked() instead of Clicked?.Invoke() — NullReferenceException if no subscribers.
  • Mixing Action/Func with custom delegates needlessly — use built-in types for new code.
  • Forgetting that += and -= reassigndelegate -= specific removes one subscriber by reference.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…