Delegates and Events
A delegate is a type whose value refers to a method. It turns "call a method" into "pass a method as data": store it in a variable, put it in a list, hand it to another method as an argument. Events, LINQ, callbacks, and the whole functional style in C# rest on that single idea.
What separates a delegate from an ordinary call is that the compiler checks the signature. A delegate fixes a set of parameters and a return type, and only a method with a matching signature can be assigned to it; a mismatch is rejected at compile time, not at runtime. From that base grow the layers: one delegate can hold a whole list of targets (multicast), you rarely need to declare your own type (Func/Action/Predicate), a method body can be written inline (lambdas and anonymous methods), and a lambda that captures an outer variable becomes a closure. The full map lives in the layers below.
Topic map
- Delegates — a type-safe reference to a method, declaring the signature, assignment and invocation, the target object.
- Multicast delegates — the invocation list,
+=/-=, execution order, the return-value and exception traps. - Func, Action, Predicate — built-in generic delegates instead of custom types.
- Lambdas — the
=>syntax, variable capture, closures and their lifetime. - Anonymous methods — the older
delegate { }syntax and how it relates to lambdas. - Binding time — early (static) vs late binding,
dynamicand reflection.
Common traps
| Mistake | Consequence |
|---|---|
| Confusing a delegate with an interface | A delegate references one method by signature; it does not describe a whole type's contract |
| Expecting all return values from a multicast delegate | Only the last target's result survives; earlier returns are discarded |
Thinking += replaces the target | += appends a method to the invocation list, -= removes one; there is no replace |
Declaring a custom delegate type instead of Func/Action | Boilerplate where a built-in generic delegate already fits |
| Thinking a closure copies the variable | The lambda shares the live variable by reference; changes are visible on both sides |
| Assuming late binding is faster than early | The opposite: dynamic/reflection drop compile-time checks and add runtime overhead |
Interview relevance
Delegates are one of the most common C# interview topics because the whole "method as a value" model hangs off them: events, LINQ, callbacks, functional style. A candidate who explains a delegate as "a type-safe reference to a method whose signature the compiler checks" immediately stands apart from "well, it's a function pointer".
Typical checks:
- What a delegate is, and how it differs from an interface and from a plain method call.
- How a multicast delegate's invocation list behaves: order, the single surviving return value, stopping on an exception.
- When to reach for
Func/Action/Predicateversus declaring your own type. - What a closure is and why it shares the live variable, not a copy.
- The difference between early and late binding and the cost of
dynamic.
Common wrong answer: "a multicast delegate gives me all the results and runs every target even if one fails". That opens the discussion that only the last return survives and that an exception cuts the walk short — and that to collect every result you must iterate the targets yourself via GetInvocationList().