Events
Events wrap delegates with publish-subscribe semantics — only the declaring class can raise (invoke) them; external code subscribes with +=.
Introduction
Events wrap delegates with publish-subscribe semantics — only the declaring class can raise (invoke) them; external code subscribes with +=. WPF, WinForms, and domain-driven designs use events for loose coupling.
EventHandler and EventHandler<TEventArgs> are standard patterns. Interviewers warn about memory leaks when subscribers forget -= and thread safety of raise.
The story
When a customer completes checkout on an e-commerce site, the order service raises an OrderPlaced event so inventory, email, and analytics modules react without the order service knowing about them directly. Email sends a confirmation; warehouse reserves stock — all subscribed via += handlers.
Understanding the topic
Key concepts
- event keyword restricts invoke to containing class.
- EventHandler
standard signature. - Custom EventArgs carry payload properties.
- Nullable event — check null before invoke.
- Record EventArgs for immutability.
- Async events rare — prefer IObservable or channels.
Step-by-step explanation
- Publisher declares public event EventHandler
SomethingHappened; - Subscriber registers handler += OnSomething;
- Publisher calls SomethingHappened?.Invoke(this, args);
- Unsubscribe -= to release reference.
- Thread-safe raise may copy delegate to local var.
- Domain events in DDD before integration events.
Practical code example
Order placed domain event with typed EventArgs:
namespace TechLearningPro.Events;public sealed class OrderPlacedEventArgs(Guid orderId, decimal total) : EventArgs{public Guid OrderId { get; } = orderId;public decimal Total { get; } = total;}public sealed class OrderService{public event EventHandler<OrderPlacedEventArgs>? OrderPlaced;public void PlaceOrder(Guid orderId, decimal total){// persist order...OrderPlaced?.Invoke(this, new OrderPlacedEventArgs(orderId, total));}}
Line-by-line code explanation
OrderPlacedEventArgs(Guid orderId, decimal total) : EventArgscarries event payload immutably.public Guid OrderId { get; }andTotal { get; }expose read-only event data.event EventHandler<OrderPlacedEventArgs>? OrderPlaceddeclares the event — external code can subscribe but not invoke.PlaceOrder(Guid orderId, decimal total)is the publisher method that raises the event after persisting.OrderPlaced?.Invoke(this, new OrderPlacedEventArgs(orderId, total))safely notifies all subscribers.?.beforeInvokeskips the call when no handlers are registered — no null check needed.thisas sender identifies which service raised the event.new OrderPlacedEventArgs(...)creates fresh args for each notification.
Key takeaway: ?.Invoke safe if no subscribers. In ASP.NET prefer MediatR notifications over events for request-scoped DI.
Real-world use
Where you'll use this in production
- UI button click handlers (legacy desktop).
- Domain events notifying read model updaters.
- FileSystemWatcher Changed event.
- Timer Elapsed notifications.
Best practices
- Always unsubscribe in IDisposable/dispose pattern.
- Use weak events for long-lived publishers (WPF).
- Prefer MediatR/IHostedService in ASP.NET over static events.
- Make EventArgs immutable.
- Copy event delegate before invoke if multi-threaded.
Common mistakes
- Public invoke on event from outside — compile error, use method.
- Memory leak forgetting -=.
- Heavy work in UI event handler blocking thread.
- Throwing from multicast stops other handlers.
Advanced interview questions
Q1BeginnerEvent vs delegate field?
Q2BeginnerStandard EventHandler signature?
Q3IntermediateEvent memory leak scenario?
Q4IntermediateDomain events vs integration events?
Q5AdvancedReplace static events in ASP.NET with DI-friendly pattern.
Summary
Events encapsulate delegate publish-subscribe. Only publisher raises; subscribers += and -=. Unsubscribe to prevent memory leaks. In modern web prefer MediatR over static events. Next: lambda expressions.