Events: Advanced Patterns & Custom EventArgs
Lesson 17 โข Advanced Track
What You'll Learn
- Create custom EventArgs classes to carry rich data with events
- Use EventHandler<T> for type-safe event declarations
- Implement custom event accessors (add/remove) for thread safety
- Build the Observer pattern using C# events
- Manage event subscriptions and prevent memory leaks
- Design event-driven architectures for decoupled systems
๐ก Real-World Analogy
Events are like a notification system. A news publisher (event source) doesn't know or care who subscribes โ it just broadcasts when something happens. Subscribers (event handlers) sign up to receive notifications and can unsubscribe any time. Custom EventArgs are like the news article attached to each notification โ they carry the details of what happened.
1. Custom EventArgs
While simple events can pass EventArgs.Empty, real applications need to carry data. Create a class that extends EventArgs with properties for the data subscribers need โ order IDs, amounts, timestamps, etc.
Custom EventArgs
Create rich event data with OrderEventArgs and multiple subscribers.
using System;
// Custom EventArgs carry data about the event
class OrderEventArgs : EventArgs
{
public string OrderId { get; }
public decimal Amount { get; }
public DateTime Timestamp { get; }
public OrderEventArgs(string orderId, decimal amount)
{
OrderId = orderId;
Amount = amount;
Timestamp = DateTime.Now;
}
}
class OrderService
{
// Declare event with custom EventArgs
public event EventHandler<OrderEventArgs>? OrderPlaced;
public
...2. Custom Event Accessors
By default, C# auto-generates add and remove accessors for events. You can write custom accessors to add thread safety with lock, logging, or subscription validation โ similar to property get/set.
Custom Event Accessors
Implement thread-safe add/remove with logging.
using System;
using System.Collections.Generic;
class EventBus
{
// Custom event accessors for thread-safe subscription
private readonly object _lock = new object();
private EventHandler<string>? _messageReceived;
public event EventHandler<string> MessageReceived
{
add
{
lock (_lock)
{
_messageReceived += value;
Console.WriteLine($" โ
Subscriber added (total: {_messageReceived?.GetInvocationList().Leng
...3. Observer Pattern with Events
The Observer pattern is built into C# via events. A subject (sensor) notifies all observers (displays) when its state changes. Events provide natural decoupling โ the sensor doesn't import or know about the display classes.
Observer Pattern
Build a temperature monitoring system with multiple observers.
using System;
using System.Collections.Generic;
// Observer pattern with IObservable/IObserver
class TemperatureSensor
{
public event EventHandler<double>? TemperatureChanged;
private double _temperature;
public double Temperature
{
get => _temperature;
set
{
if (Math.Abs(_temperature - value) > 0.01)
{
_temperature = value;
TemperatureChanged?.Invoke(this, value);
}
}
}
}
c
...Pro Tips
- ๐ก Always use ?.Invoke(): Events can be null if no subscribers exist.
MyEvent?.Invoke(this, args)prevents crashes. - ๐ก Events vs delegates: Events restrict external code to only
+=and-=. Delegates allow external code to invoke or reassign โ events are safer for public APIs. - ๐ก Unsubscribe to prevent leaks: If a short-lived object subscribes to a long-lived object's event, it won't be garbage collected. Always unsubscribe in
Dispose(). - ๐ก Use record for EventArgs:
record OrderEventArgs(string Id, decimal Amount) : EventArgs;is concise in C# 9+.
Common Mistakes
- Invoking without null check:
OrderPlaced(this, args)throws if no subscribers. Always useOrderPlaced?.Invoke(this, args). - Memory leaks from subscriptions: Lambda event handlers are hard to unsubscribe. Store them in a variable if you need to unsubscribe later.
- Raising events from outside the class: Events can only be invoked from within the declaring class โ this is by design for encapsulation.
- Exception in one handler stops others: If handler A throws, handlers B and C won't fire. Consider wrapping invocations in try-catch loops.
๐ Lesson Complete
- โ
Custom
EventArgscarry rich data: order IDs, amounts, timestamps - โ
EventHandler<T>provides type-safe event declarations - โ
Custom
add/removeaccessors enable thread-safe subscription management - โ The Observer pattern is natively supported through C# events
- โ
Always use
?.Invoke()and unsubscribe to prevent memory leaks - โ Next lesson: Expression Trees โ building dynamic logic at runtime
Sign up for free to track which lessons you've completed and get learning reminders.