Reading — step 1 of 6
Learn
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 nothingFunc<..., TResult>— takes args, returns TResultPredicate<T>— takes T, returns bool (essentiallyFunc<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 ofClicked?.Invoke()— NullReferenceException if no subscribers. - Mixing
Action/Funcwith custom delegates needlessly — use built-in types for new code. - Forgetting that
+=and-=reassign —delegate -= specificremoves 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…