C# Events Basics
Introduction
Events are a powerful feature in C# that enables communication between objects. They provide a way for a class to notify other classes when something interesting happens, without needing to know which classes are interested in that notification. Events are built on the foundation of delegates and follow the publisher-subscriber pattern, where one class (the publisher) raises events that other classes (subscribers) can respond to.
In this tutorial, you'll learn:
- What events are and why they're useful
- How events relate to delegates
- How to declare, subscribe to, and raise events
- Best practices for using events
Understanding Events
What is an Event?
An event is a notification sent by an object to signal the occurrence of an action. Events in C# are based on delegates and provide a way to implement the observer pattern. Let's break down the concept:
- Publisher: The class that contains and raises the event
- Subscriber: The class that registers for and handles the event
- Event Handler: The method that gets called when the event is raised
Events vs Delegates
Events are built on delegates, but they add some important restrictions:
- Events can only be invoked from within the class that declares them
- External classes can only subscribe or unsubscribe from events, not trigger them
- Events cannot be assigned directly (unlike delegates)
Declaring Events
Basic Event Declaration
Here's how you declare an event using a delegate:
// Step 1: Define a delegate type for the event
public delegate void MessageReceivedEventHandler(string message);
// Step 2: Declare the event using the delegate
public event MessageReceivedEventHandler MessageReceived;
Using EventHandler Delegate
.NET provides a built-in EventHandler
delegate type that you can use:
// Using the built-in EventHandler
public event EventHandler ButtonClicked;
// Using the generic EventHandler<T> for custom event data
public event EventHandler<string> MessageReceived;
Subscribing to Events
To subscribe to an event, you use the +=
operator to add an event handler method:
// Assuming 'publisher' is an instance of the class containing the event
publisher.MessageReceived += HandleMessageReceived;
// The event handler method
private void HandleMessageReceived(string message)
{
Console.WriteLine($"Message received: {message}");
}
Raising Events
To raise (trigger) an event, you invoke it like a delegate. However, it's important to check if the event has any subscribers first:
// Inside the publisher class
protected virtual void OnMessageReceived(string message)
{
// Check if there are any subscribers before raising the event
MessageReceived?.Invoke(message);
}
public void ProcessMessage(string message)
{
// Some processing logic
Console.WriteLine($"Processing: {message}");
// Raise the event
OnMessageReceived(message);
}
Complete Example
Let's create a complete example of a notification system:
using System;
namespace EventsDemo
{
// Publisher class
public class NotificationService
{
// Event declaration using EventHandler<T>
public event EventHandler<string> NotificationReceived;
// Method to raise the event
protected virtual void OnNotificationReceived(string message)
{
NotificationReceived?.Invoke(this, message);
}
// Method that triggers the notification
public void SendNotification(string message)
{
Console.WriteLine($"Sending notification: {message}");
// Raise the event
OnNotificationReceived(message);
}
}
// Subscriber class
public class User
{
public string Name { get; set; }
public User(string name)
{
Name = name;
}
// Event handler method
public void HandleNotification(object sender, string message)
{
Console.WriteLine($"{Name} received notification: {message}");
}
}
class Program
{
static void Main(string[] args)
{
// Create publisher
var notificationService = new NotificationService();
// Create subscribers
var user1 = new User("Alice");
var user2 = new User("Bob");
// Subscribe to the event
notificationService.NotificationReceived += user1.HandleNotification;
notificationService.NotificationReceived += user2.HandleNotification;
// Raise the event
notificationService.SendNotification("System maintenance scheduled for tomorrow.");
// Unsubscribe user1
notificationService.NotificationReceived -= user1.HandleNotification;
// Raise the event again
notificationService.SendNotification("New feature released!");
}
}
}
Output:
Sending notification: System maintenance scheduled for tomorrow.
Alice received notification: System maintenance scheduled for tomorrow.
Bob received notification: System maintenance scheduled for tomorrow.
Sending notification: New feature released!
Bob received notification: New feature released!
Custom Event Arguments
For more complex event data, you can create custom event argument classes that derive from EventArgs
:
public class NotificationEventArgs : EventArgs
{
public string Message { get; set; }
public DateTime Timestamp { get; set; }
public NotificationType Type { get; set; }
public NotificationEventArgs(string message, NotificationType type)
{
Message = message;
Type = type;
Timestamp = DateTime.Now;
}
}
public enum NotificationType
{
Information,
Warning,
Error
}
// Using the custom event args
public class NotificationService
{
public event EventHandler<NotificationEventArgs> NotificationReceived;
protected virtual void OnNotificationReceived(NotificationEventArgs e)
{
NotificationReceived?.Invoke(this, e);
}
public void SendNotification(string message, NotificationType type)
{
var eventArgs = new NotificationEventArgs(message, type);
OnNotificationReceived(eventArgs);
}
}
Real-World Applications
Events are commonly used in many scenarios:
1. User Interface Applications
In GUI applications, events handle user interactions:
// WinForms example
button.Click += Button_Click;
private void Button_Click(object sender, EventArgs e)
{
MessageBox.Show("Button clicked!");
}
2. Progress Reporting
Events can report progress during long operations:
public class FileProcessor
{
public event EventHandler<int> ProgressChanged;
public void ProcessFiles(string[] files)
{
for (int i = 0; i < files.Length; i++)
{
// Process file
ProcessFile(files[i]);
// Report progress
int progressPercentage = (i + 1) * 100 / files.Length;
ProgressChanged?.Invoke(this, progressPercentage);
}
}
private void ProcessFile(string filePath)
{
// File processing logic
}
}
3. Application State Changes
Events can notify about changes in application state:
public class ConnectionManager
{
private bool _isConnected;
public event EventHandler<bool> ConnectionStatusChanged;
public bool IsConnected
{
get { return _isConnected; }
set
{
if (_isConnected != value)
{
_isConnected = value;
ConnectionStatusChanged?.Invoke(this, _isConnected);
}
}
}
public void Connect()
{
Console.WriteLine("Connecting to server...");
// Connection logic here
IsConnected = true;
}
public void Disconnect()
{
Console.WriteLine("Disconnecting from server...");
// Disconnection logic here
IsConnected = false;
}
}
Best Practices
-
Always check for null before invoking an event:
csharpSomeEvent?.Invoke(this, eventArgs);
-
Create a protected virtual OnEventName method for raising events:
csharpprotected virtual void OnSomeEvent(EventArgs e)
{
SomeEvent?.Invoke(this, e);
} -
Use EventHandler and
EventHandler<T>
instead of custom delegates when possible -
Consider thread safety when working with events in multi-threaded applications
-
Properly unsubscribe from events to prevent memory leaks, especially with long-lived objects
Summary
Events in C# provide a powerful mechanism for communication between objects using the publisher-subscriber pattern. They are based on delegates but add restrictions that make them safer and more encapsulated. By using events, you can create loosely coupled systems where components can interact without having direct dependencies on each other.
You've learned how to:
- Declare events using delegates and built-in
EventHandler
types - Subscribe to and unsubscribe from events
- Raise events safely
- Create custom event arguments
- Apply events in real-world scenarios
Exercises
- Create a
Timer
class that raises an event every second and subscribes to it from a different class. - Implement a
StockMonitor
class that raises events when stock prices change by more than 5%. - Extend the notification example to include different notification types and priority levels.
- Create a simple button click counter that uses events to update the count display.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)