C# Events Explained: Build Event-Driven Applications
Events allow objects in C# to notify other objects when something happens. They are commonly used for user interactions, application notifications, status changes, and communication between loosely coupled components.
What is an Event in C#?
An event is a mechanism that allows a class, known as the publisher, to notify other classes, known as subscribers, when a specific action occurs.
class Button
{
public event Action? Clicked;
public void Click()
{
Clicked?.Invoke();
}
}
Publisher and Subscriber
The publisher defines and raises an event, while subscribers register methods that should execute when the event occurs.
Button button = new Button();
button.Clicked += OnButtonClicked;
button.Click();
static void OnButtonClicked()
{
Console.WriteLine("Button was clicked");
}
Why Use Events?
- Create loosely coupled components
- Notify multiple subscribers
- Implement event-driven behavior
- Separate event producers from consumers
- Build reusable application components
- React to changes without tightly connecting classes
Declaring an Event
An event can be declared using the event keyword with a compatible delegate type.
class OrderService
{
public event Action? OrderCompleted;
public void CompleteOrder()
{
Console.WriteLine("Order completed");
OrderCompleted?.Invoke();
}
}
Subscribing to an Event
The += operator subscribes a method to an event. Once subscribed, the method is called whenever the publisher raises the event.
OrderService service = new OrderService();
service.OrderCompleted += HandleOrderCompleted;
service.CompleteOrder();
static void HandleOrderCompleted()
{
Console.WriteLine("Sending confirmation");
}
Unsubscribing from an Event
The -= operator removes a previously subscribed handler. Unsubscribing is important when a subscriber should no longer receive notifications.
service.OrderCompleted += HandleOrderCompleted;
service.OrderCompleted -= HandleOrderCompleted;
EventHandler
EventHandler is the standard .NET delegate pattern for events that do not require custom event data.
class DownloadService
{
public event EventHandler? DownloadCompleted;
public void Download()
{
Console.WriteLine("Downloading...");
DownloadCompleted?.Invoke(this, EventArgs.Empty);
}
}
Handling EventHandler
DownloadService service = new DownloadService();
service.DownloadCompleted += OnDownloadCompleted;
service.Download();
static void OnDownloadCompleted(object? sender, EventArgs e)
{
Console.WriteLine("Download completed");
}
Custom Event Arguments
When an event needs to provide additional information, a custom EventArgs class can be created.
class OrderCompletedEventArgs : EventArgs
{
public int OrderId { get; }
public decimal Total { get; }
public OrderCompletedEventArgs(int orderId, decimal total)
{
OrderId = orderId;
Total = total;
}
}
EventHandler
EventHandler
class OrderService
{
public event EventHandler<OrderCompletedEventArgs>? OrderCompleted;
public void CompleteOrder(int orderId, decimal total)
{
var args = new OrderCompletedEventArgs(orderId, total);
OrderCompleted?.Invoke(this, args);
}
}
Subscribing to Custom Events
OrderService service = new OrderService();
service.OrderCompleted += OnOrderCompleted;
service.CompleteOrder(1001, 499.99m);
static void OnOrderCompleted(
object? sender,
OrderCompletedEventArgs e)
{
Console.WriteLine($"Order: {e.OrderId}");
Console.WriteLine($"Total: {e.Total}");
}
Event Flow
| Component | Responsibility | Example |
|---|---|---|
| Publisher | Defines and raises event | OrderService |
| Event | Represents notification | OrderCompleted |
| Subscriber | Registers a handler | EmailService |
| Event Handler | Runs when event occurs | OnOrderCompleted |
Multiple Subscribers
Multiple objects can subscribe to the same event. When the publisher raises the event, all registered handlers are invoked.
service.OrderCompleted += SendEmail;
service.OrderCompleted += UpdateDashboard;
service.OrderCompleted += WriteLog;
service.CompleteOrder(1001, 250m);
Events vs Delegates
| Feature | Event | Delegate |
|---|---|---|
| Purpose | Publish notifications | Reference callable methods |
| Subscription | Uses += and -= | Can be assigned directly |
| Typical Use | Event-driven communication | Callbacks and behavior |
| Encapsulation | Subscribers cannot normally raise the event | Delegate variable can invoke its target |
Common Event Mistakes
- Forgetting to unsubscribe long-lived event subscriptions
- Using events when a simple method call is enough
- Creating custom delegate types unnecessarily
- Raising an event without checking for subscribers
- Putting too much business logic inside event handlers
- Creating event chains that are difficult to trace
Event Best Practices
- Follow the standard EventHandler pattern for public .NET-style events
- Use EventHandler
when event data is required - Keep event arguments focused and meaningful
- Use null-safe invocation when an event may have no subscribers
- Unsubscribe when the subscriber lifetime requires it
- Keep event handlers small and focused
Real-World Applications
- Button click notifications
- Order completion events
- File processing status
- Download progress
- Application state changes
- Logging and monitoring
- Notification systems
Practice Exercises
- Create a simple ButtonClicked event
- Build an OrderCompleted event
- Subscribe multiple handlers to one event
- Create custom EventArgs
- Implement an EventHandler
event - Practice subscribing and unsubscribing
- Build a small notification system using events
Conclusion
Events provide a clean way for C# objects to communicate without creating unnecessary dependencies between components. By understanding publishers, subscribers, EventHandler, and custom event arguments, developers can build flexible and maintainable event-driven applications.