Skip to main content

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:

csharp
// 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:

csharp
// 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:

csharp
// 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:

csharp
// 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:

csharp
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:

csharp
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:

csharp
// 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:

csharp
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:

csharp
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

  1. Always check for null before invoking an event:

    csharp
    SomeEvent?.Invoke(this, eventArgs);
  2. Create a protected virtual OnEventName method for raising events:

    csharp
    protected virtual void OnSomeEvent(EventArgs e)
    {
    SomeEvent?.Invoke(this, e);
    }
  3. Use EventHandler and EventHandler<T> instead of custom delegates when possible

  4. Consider thread safety when working with events in multi-threaded applications

  5. 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

  1. Create a Timer class that raises an event every second and subscribes to it from a different class.
  2. Implement a StockMonitor class that raises events when stock prices change by more than 5%.
  3. Extend the notification example to include different notification types and priority levels.
  4. 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! :)