Intermediate · Exceptions, delegates, events and async · topic 28 of 43 · 25 min
Events: letting objects notify each other
Explanation
Why events?
When a customer places an order, many things should happen: send an SMS, send an email, update stock, award loyalty points. If the Order class called all of these directly, it would depend on every other part of the system, and adding a new reaction would mean editing Order. With an event, Order just announces "order placed", and each part subscribes on its own.
Declaring and raising an event
public class OrderPlacedEventArgs : EventArgs
{
public int OrderId { get; }
public decimal Amount { get; }
public OrderPlacedEventArgs(int orderId, decimal amount) { OrderId = orderId; Amount = amount; }
}
public class OrderService
{
public event EventHandler<OrderPlacedEventArgs>? OrderPlaced;
public void Place(int orderId, decimal amount)
{
// ... save the order ...
OnOrderPlaced(new OrderPlacedEventArgs(orderId, amount));
}
protected virtual void OnOrderPlaced(OrderPlacedEventArgs e) => OrderPlaced?.Invoke(this, e);
}Subscribing and unsubscribing
var service = new OrderService();
service.OrderPlaced += (sender, e) => Console.WriteLine($"SMS: order {e.OrderId} confirmed");
service.OrderPlaced += SendEmail;
service.Place(1001, 799m);
service.OrderPlaced -= SendEmail; // stop listening
service.Place(1002, 250m);
static void SendEmail(object? sender, OrderPlacedEventArgs e)
=> Console.WriteLine($"Email: receipt for Rs {e.Amount}");What the event keyword adds
An event is a delegate with a protective wrapper. Outside code can only add (+=) and remove (-=) handlers. It cannot raise the event, and it cannot wipe out everyone else's handlers with =. Only the class that declares the event can raise it.
// service.OrderPlaced = null; // error: only += and -= allowed outside
// service.OrderPlaced?.Invoke(null, args); // error: cannot raise from outsideWhere you will meet events
- Desktop UI: button.Click += OnSaveClicked; in Windows Forms and WPF.
- Timers: System.Timers.Timer raises Elapsed every interval.
- File watching: FileSystemWatcher raises Created and Changed when files appear or change.
- Games: Unity code often uses C# events (or UnityEvents) for "player died" or "coin collected".
Common mistakes and fixes
- Raising without ?.: OrderPlaced(this, e) throws NullReferenceException when nobody subscribed. Use OrderPlaced?.Invoke(this, e).
- Never unsubscribing: the publisher keeps a reference to each subscriber, so long-lived publishers can keep short-lived objects alive (a memory leak). Use -= when the subscriber is done.
- Trying to unsubscribe a lambda by writing the same lambda again: it is a different delegate, so nothing is removed. Store the lambda in a variable, or use a named method.
- Slow work inside handlers: handlers run one after another on the same thread; a slow one delays the rest.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Events: letting objects notify each other · 1/7
In this lesson objects will notify each other using events.
Prefer a video by a teacher? Find free videos on “C# events EventHandler publisher subscriber tutorial” on YouTube ↗
In real life: A low-stock alert at a kirana store
A grocery store's inventory class raises a LowStock event when an item falls below its reorder level. The shop owner's phone app and the supplier ordering module both subscribe, and the inventory code never needs to know about either.
var inv = new Inventory();
inv.LowStock += (_, e) => Console.WriteLine($"Owner alert: only {e.Left} {e.Item} left");
inv.LowStock += (_, e) => Console.WriteLine($"Auto-order: 50 more {e.Item}");
inv.Sell("Atta 5kg", 7);
inv.Sell("Atta 5kg", 2);
public record LowStockEventArgs(string Item, int Left);
public class Inventory
{
private readonly Dictionary<string, int> _stock = new() { ["Atta 5kg"] = 12 };
public event EventHandler<LowStockEventArgs>? LowStock;
public void Sell(string item, int qty)
{
_stock[item] -= qty;
if (_stock[item] < 5)
LowStock?.Invoke(this, new LowStockEventArgs(item, _stock[item]));
}
}Practice (0/4 done)
Which operator subscribes a handler to an event?
Why raise an event with Changed?.Invoke(this, e) instead of Changed(this, e)?
What can code OUTSIDE the declaring class do with an event?
Create a class Thermostat with a Temperature property. When the temperature is set above 40, raise an event HeatAlert with EventHandler<double> carrying the temperature. Subscribe two handlers: one prints a warning, one prints "Turning on cooler". Set the temperature to 35 and then 42.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- async and await: waiting without blockingExceptions, delegates, events and async · same module
- Exceptions: try, catch, finally and custom exceptionsExceptions, delegates, events and async · same module
- Events: reacting to the userFrontend Web Development · JavaScript in the browser: DOM and events
- Event-driven design: API Gateway, SQS, SNS, EventBridge and Step FunctionsCloud Computing (AWS, Azure, Google Cloud) · Serverless, monitoring and cost control
- *args and **kwargsPython Programming · Functions
- Errors and exceptionsPython Programming · Errors, modules and the standard library
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is the signature of an EventHandler<T> handler?
Answer: void (object? sender, T e)
Handlers receive the sender and the event data.
What is the main benefit of events?
Answer: The publisher does not need to know who reacts, so parts stay loosely coupled
Events decouple the announcer from the listeners.
Why might a forgotten subscription cause a memory leak?
Answer: The publisher keeps a reference to the subscriber, keeping it alive
As long as the publisher lives and holds the handler, the subscriber cannot be collected.
What is a good name for an event raised after a payment fails?
Answer: PaymentFailed
Events are usually named in the past tense.
What is event delegation?Related
Answer: Putting one listener on a parent and finding the clicked child with event.target
Because events bubble up, one listener on the parent can handle all its children, even ones added later.
A worker received an SQS message but crashed before deleting it. What happens?Related
Answer: After the visibility timeout ends, the message becomes visible again
Messages are only removed when deleted. When the visibility timeout ends, another consumer can receive it. It goes to the DLQ only after maxReceiveCount receives.
From Event-driven design: API Gateway, SQS, SNS, EventBridge and Step Functions
In def f(*args), what type is args?Related
Answer: tuple
A single star collects the extra positional arguments into a tuple.
From *args and **kwargs
What does int("abc") raise?Related
Answer: ValueError
The type (a string) is fine for int(), but this value cannot become a number, so it is a ValueError.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Exceptions, delegates, events and async” before its exam: async and await: waiting without blocking, Files and JSON with System.Text.Json.
- Module 7 exam: Exceptions, delegates, events and async · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Web APIs with ASP.NET Core and EF Core. 43 topics left in the course before the final exam.