Delegates & Events
Delegates, multicast, Func/Action/Predicate, lambdas, binding, and dynamic dispatch.
7 questions
JuniorTheoryVery commonWhat is a delegate in C#, and how is it declared and invoked?
What is a delegate in C#, and how is it declared and invoked?
A delegate is a type-safe reference to a method — a typed function pointer. You declare it as a type with a fixed signature, assign any method whose parameters and return type match, then call the delegate variable like a method to invoke the referenced target. The compiler checks signatures, so a mismatched method is rejected.
Common mistakes
- ✗Confusing a delegate with an interface — a delegate references one method, not a whole type contract
- ✗Assuming any method can be assigned, ignoring that the signature must match the delegate type
- ✗Thinking the delegate copies the method body rather than holding a reference to the target
Follow-up questions
- →How does a delegate differ from the built-in delegate
FuncorAction? - →What does a delegate instance store besides the method — does it capture a target object?
JuniorTheoryVery commonWhat is a lambda expression in C#, and what is a closure?
What is a lambda expression in C#, and what is a closure?
A lambda like x => x * 2 is an inline anonymous function: a compact => syntax for a method body assignable to a matching delegate or an expression tree. Its body can read and mutate variables from the enclosing scope, not just its own parameters. When it captures such an outer variable it becomes a closure, and the captured variable lives on as long as the lambda does.
Common mistakes
- ✗Believing a lambda can only touch its own parameters, not variables from the enclosing scope
- ✗Assuming a closure snapshots captured variables by value instead of sharing the live variable
- ✗Forgetting a lambda is a value that must be assigned to a delegate or expression tree to be used
Follow-up questions
- →Why can a loop variable captured by a lambda surprise you across iterations?
- →When does a lambda compile to an expression tree instead of executable delegate code?
JuniorTheoryCommonWhat are the built-in delegates Func, Action, and Predicate?
What are the built-in delegates Func, Action, and Predicate?
They are generic delegate types from the standard library, so you rarely declare your own. Func<...,TResult> takes zero or more inputs and returns a value of TResult. Action<...> takes inputs but returns void. Predicate<T> takes one T and returns bool, used for tests like filtering. Preferring them keeps signatures consistent and avoids boilerplate delegate declarations.
Common mistakes
- ✗Mixing up
FuncandAction— forgetting thatActionreturnsvoidandFuncreturns a value - ✗Declaring a custom delegate type when a built-in
Func,Action, orPredicatealready fits - ✗Thinking
Predicate<T>can return any type rather than always returningbool
Follow-up questions
- →Why is
Predicate<T>largely interchangeable withFunc<T, bool>in practice? - →Where does the last type argument of
Func<T1, T2, TResult>sit in the parameter list?
MiddleTheoryOccasionalWhat are anonymous methods, and how do they relate to lambdas and closures?
What are anonymous methods, and how do they relate to lambdas and closures?
An anonymous method uses the older delegate { ... } syntax to supply an inline delegate body without naming a method; a lambda is the newer, terser form of the same idea. Both compile to a hidden method and can be assigned wherever a delegate is expected. Both also form closures — they capture outer variables by reference, sharing the live variable rather than a copy, so changes are seen on both sides.
Common mistakes
- ✗Treating anonymous methods and lambdas as unrelated rather than two syntaxes for inline delegates
- ✗Assuming captured variables are copied by value instead of shared by reference as a closure
- ✗Believing an anonymous method cannot be stored in a delegate or capture any outer state
Follow-up questions
- →When would you still reach for
delegate { }syntax over a lambda today? - →Why can capturing the same variable by reference in a loop cause a subtle bug?
MiddleTheoryOccasionalWhat is the difference between early (static) and late binding in C#?
What is the difference between early (static) and late binding in C#?
Binding is how a method call is matched to an implementation. Early or static binding resolves the call at compile time from the variable's declared type, giving type checking, IntelliSense, and fast direct calls. Late binding defers resolution to runtime against the actual object — via dynamic or reflection — so the member need not be known when compiling. You trade compile-time safety and speed for flexibility.
Common mistakes
- ✗Assuming late binding is faster, when it actually skips compile-time checks and adds runtime overhead
- ✗Thinking
dynamicstill gives compile-time type checking and IntelliSense like a static type - ✗Believing binding time is decided by value-vs-reference type rather than the declared type
Follow-up questions
- →What runtime exception replaces a compile error when a
dynamicmember does not exist? - →How does method overload resolution differ between a statically typed and a
dynamiccall?
MiddleTheoryOccasionalWhat is a multicast delegate, and how does its invocation list behave?
What is a multicast delegate, and how does its invocation list behave?
Every delegate holds an invocation list of targets. += appends a method and -= removes one, so a single delegate can reference many methods. Invoking it runs every target in the order they were added. For a value-returning delegate you only get the last target's result; the earlier returns are discarded. If any target throws, invocation stops and the remaining methods never run.
Common mistakes
- ✗Expecting all return values back, not realizing only the last target's result survives
- ✗Assuming a thrown exception is swallowed so the rest of the list still runs
- ✗Thinking
+=replaces the target instead of appending it to the invocation list
Follow-up questions
- →How can you invoke each target manually to collect every return value or isolate exceptions?
- →What does
-=do if the same method appears more than once in the invocation list?
SeniorTheoryRareWhat is dynamic dispatch, and how does it differ from static dispatch?
What is dynamic dispatch, and how does it differ from static dispatch?
Dynamic dispatch selects which method implementation runs based on the object's actual runtime type, not the declared type of the reference. For virtual methods the runtime looks the target up through the object's method table, so an overriding subclass version is chosen even via a base-type reference. Static dispatch instead fixes the target at compile time from the declared type, so non-virtual calls cannot be overridden polymorphically.
Common mistakes
- ✗Thinking dispatch uses the reference's declared type rather than the object's actual runtime type
- ✗Assuming every method is dynamically dispatched, ignoring that non-virtual calls bind statically
- ✗Confusing dynamic dispatch with
dynamic-based late binding via reflection or the DLR
Follow-up questions
- →How does the method table make a
virtualcall only marginally slower than a direct one? - →What does
sealedon an override let the JIT do to a previously virtual call site?