Delegates, Events & Lambda Expressions
csharp2-2's LINQ lambdas worked without ever asking what a lambda actually compiles to in C#. This chapter answers that — and the answer is a real, structural departure from java2-3's own model.
Delegates — A Type-Safe Function Pointer
A delegate declares a type describing a method signature — genuinely similar in spirit to C's own function pointers, but type-checked at compile time the way a raw C function pointer never is. A Calculator variable can hold any method (or lambda) matching that exact signature.
Built-in Delegate Types — Func<> and Action<>
Almost nobody declares a custom delegate type like Calculator in real code — Func<T1, ..., TResult> and Action<T1, ...> cover nearly every shape needed, genuinely comparable to java2-3's own built-in java.util.function interfaces.
Lambdas Assigned to Delegates — The Real Mechanism
Here's the reveal, and it's a real divergence from java2-3: in Java, a lambda compiles to an anonymous implementation of a functional interface's single method — genuinely an OOP mechanism, an object with one method. In C#, a lambda assigned to a delegate compiles to something different — a real compiler-generated method (often a private static method), and the delegate variable is a type-safe reference to that method, much closer in spirit to a function pointer than to an interface implementation. The syntax at the call site looks nearly identical in both languages; the mechanism underneath genuinely isn't.
Multicast Delegates
A delegate variable can reference multiple methods at once via +=, invoking all of them in order when called — genuinely unique to C#, with no direct Java equivalent at all. This multicast capability is the exact mechanism the next feature is built on.
The event Keyword
event restricts a multicast delegate field: outside code can only +=/-= subscribers, never invoke it directly or reassign it with = — only the declaring class can. It's a genuine publish/subscribe pattern baked directly into the language, contrasted against Java's typical approach of hand-rolling a List<Listener> and manually iterating it to fire an event.
| Mechanism | C (c2-7) | Java (java2-3) | C# |
|---|---|---|---|
| What it really is | a raw, untyped pointer to a function | an anonymous interface implementation | a type-safe reference to a method |
| Compile-time type checking | none — mismatched signatures compile | yes — via the functional interface | yes — via the delegate type |
| Referencing multiple functions at once | not built in | not built in | multicast delegates, built in (+=) |
| Built-in publish/subscribe | none | none — hand-rolled listener lists | event keyword |
delegate declaration is worth it mainly when a genuinely descriptive name adds real clarity over Func<int, int, int> — otherwise the built-in types cover the shape just as well with less ceremony.
null, not an empty, safely-invokable list — calling it directly throws NullReferenceException. The Clicked?.Invoke() pattern, using csharp1-3's own null-conditional operator, exists specifically to guard against this.
Coding Challenges
Declare a Func<int, int, int> that multiplies its two arguments and an Action<string> that prints a message, then call both and show the results.
📄 View solutionCreate an Action<string> delegate variable, add two separate lambdas to it using +=, then invoke it once and show both lambdas ran in order.
📄 View solutionWrite a class Alarm with a public event Action Triggered and a method Sound() that raises it safely using the null-conditional operator. Demonstrate calling Sound() both with and without a subscriber attached, showing neither call throws.
📄 View solutionChapter 3 Quick Reference
- A delegate is a type-safe method reference — compile-time checked, unlike c2-7's raw C function pointers
- Func<>/Action<>/Predicate<> cover nearly every shape, avoiding custom delegate declarations
- A C# lambda compiles to a real referenced method — a different mechanism from java2-3's anonymous interface implementation, despite near-identical call-site syntax
- Multicast delegates (+=) reference multiple methods at once, invoked in order — no Java equivalent
- event restricts a multicast delegate to +=/-= from outside code — a built-in publish/subscribe pattern Java hand-rolls instead
- Guard event invocation with ?.Invoke() — an unsubscribed event is null, not an empty list
- Next chapter: async/await — first-class language syntax since C# 5, contrasted against java2-5's own concurrency tools