Delegates, Func, Action and Events
Passing a method around like a value, and telling others when something happens
Guess first
Pick an answer. You will see if you were right at the end of the lesson.
What is the difference between Func<int, bool> and Action<int>?
Real-world analogy
What is it?
A delegate is a type that represents a method. A method can then be stored in a variable, passed as an argument and called later. Func and Action are ready-made delegate types, and a lambda is the short way to write one. An event is a controlled delegate for publish and subscribe. The owner raises it, and others can only subscribe or unsubscribe.
Real-world relevance
An order service raises OrderPlaced. An email sender, a stock updater and an audit logger each subscribe, and the service knows none of them. A classic memory leak is a long-lived singleton that raises an event and a short-lived page that never unsubscribes.
Key points
- A delegate holds a method — A delegate is a variable whose value is a method. You can pass it to other code, which calls it later.
- Func and Action — Func<int, bool> takes an int and returns a bool. Action<string> takes a string and returns nothing. Predicate<T> has the same shape as Func<T, bool>, but it is a separate type.
- Lambdas are short methods — The lambda o => o.Total > 100 is a method with no name. You write it inline where a delegate is expected.
- Closures capture variables — A lambda can use variables from the code around it. It captures the variable itself, not a copy of its value at that moment.
- The captured loop variable trap — Lambdas created in a loop that capture one shared variable all see its final value. With foreach, modern C# gives each iteration a new variable. With a for loop it does not.
- Events wrap a delegate safely — An event lets outside code add (+=) or remove (-=) a handler, but only the owner can raise it. This stops others from firing or wiping the list.
- Unsubscribe to avoid leaks — The publisher holds a reference to each subscriber. A forgotten += keeps the subscriber alive as long as the publisher lives.
- You already use them — LINQ Where takes a Func. ASP.NET Core middleware and minimal API handlers are delegates. Callbacks and Task.Run use Action.
Quick check
Answer from memory — scrolling up first makes it stick less.
Why use an event instead of a public Action field?
A short-lived object subscribes to a long-lived singleton event and never unsubscribes. What happens?
Code example
public record Order(int Id, decimal Total);
public class OrderBook
{
private readonly List<Order> _orders = new();
public event EventHandler<Order>? OrderPlaced;
public void Place(Order order)
{
_orders.Add(order);
OrderPlaced?.Invoke(this, order);
}
public IEnumerable<Order> Find(Func<Order, bool> filter) => _orders.Where(filter);
}
public class Mailer
{
public void OnOrderPlaced(object? sender, Order order) =>
Console.WriteLine($"Email sent for order {order.Id}");
}
public static class Demo
{
public static void Run()
{
var book = new OrderBook();
var mailer = new Mailer();
book.OrderPlaced += mailer.OnOrderPlaced;
book.Place(new Order(1, 250m));
var big = book.Find(o => o.Total > 100m).ToList();
book.OrderPlaced -= mailer.OnOrderPlaced;
}
}Line-by-line walkthrough
- 1. OrderPlaced is an event. Outside code can use += and -= on it, but cannot call it.
- 2. Place adds the order, then raises the event with Invoke. The question mark skips the call if nobody subscribed.
- 3. Find takes a Func of Order to bool. The caller decides what counts as a match.
- 4. Mailer.OnOrderPlaced is a normal method whose shape matches the event handler.
- 5. In Demo.Run, += subscribes the mailer, so Place now causes an email message.
- 6. The lambda in Find is a nameless method. The last -= removes the subscription so the mailer can be freed.
Spot the bug
public class DashboardWidget
{
public DashboardWidget(OrderBook book)
{
book.OrderPlaced += Refresh;
}
private void Refresh(object? sender, Order order) { /* update screen */ }
}
// book is a singleton; a new widget is created on every page visitNeed a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- Delegates and events in C# (Microsoft Learn)
- Lambda expressions (Microsoft Learn)
Lock it in