How to Use Generic Event Handlers in C
In modern C# development, generic event handlers provide a strongly
typed, reusable pattern for managing notifications between components
without declaring custom delegates. By leveraging the built-in
EventHandler<TEventArgs> delegate, developers can
pass strongly typed data directly to subscribers while maintaining
standard .NET design practices. This article breaks down how generic
event handlers work in C# applications, how to declare custom event
arguments, and how to publish and subscribe to these events cleanly.
The Standard
Pattern: EventHandler<TEventArgs>
Prior to generic delegates, every event requiring custom data needed
a dedicated delegate declaration. .NET simplifies this with the generic
EventHandler<TEventArgs> delegate:
public delegate void EventHandler<TEventArgs>(object? sender, TEventArgs e);This delegate expects two parameters: the source of the event
(sender) and a strongly typed object containing the event
payload (e), which typically derives from
System.EventArgs.
Defining Custom Event Data
To pass custom data with your event, define a class that inherits
from EventArgs.
public class OrderProcessedEventArgs : EventArgs
{
public int OrderId { get; }
public decimal TotalAmount { get; }
public OrderProcessedEventArgs(int orderId, decimal totalAmount)
{
OrderId = orderId;
TotalAmount = totalAmount;
}
}Publishing the Generic Event
The publishing class declares the event using
EventHandler<TEventArgs> and raises it safely using
the null-conditional operator (?.) along with
Invoke.
public class OrderService
{
// Declare the generic event
public event EventHandler<OrderProcessedEventArgs>? OrderProcessed;
public void ProcessOrder(int orderId, decimal amount)
{
// Business logic to process order...
// Raise the event
OnOrderProcessed(new OrderProcessedEventArgs(orderId, amount));
}
protected virtual void OnOrderProcessed(OrderProcessedEventArgs e)
{
OrderProcessed?.Invoke(this, e);
}
}Subscribing to the Generic Event
Subscribers attach a handler method to the event using the
+= operator. The handler method's signature must match the
generic delegate.
public class NotificationService
{
public void Subscribe(OrderService orderService)
{
orderService.OrderProcessed += HandleOrderProcessed;
}
public void Unsubscribe(OrderService orderService)
{
orderService.OrderProcessed -= HandleOrderProcessed;
}
private void HandleOrderProcessed(object? sender, OrderProcessedEventArgs e)
{
Console.WriteLine($"Order #{e.OrderId} processed for a total of ${e.TotalAmount:F2}");
}
}Subscribers can also use inline lambda expressions when simple logic is needed:
orderService.OrderProcessed += (sender, e) =>
{
Console.WriteLine($"Quick log: Order {e.OrderId} finished.");
};Key Advantages
- Compile-Time Type Safety: Event data is strongly typed, eliminating the need to cast object parameters inside event handlers.
- Reduced Boilerplate: Eliminates the need to write custom delegate declarations for every unique event.
- Consistent API: Adheres to standard .NET framework guidelines, making the code predictable and easier for other developers to consume.