C# - Events
C# - Events
Section titled “C# - Events”Events are a mechanism in C# that enables a class or object (the publisher) to notify other classes or objects (the subscribers) when something of interest occurs. This is based on the Publisher-Subscriber pattern and is fundamental for building loosely coupled and responsive applications.
Common examples include user actions in a UI (a button click), a task completing in the background, or a system state change (a temperature sensor exceeding a threshold).
The Core Components of Events
Section titled “The Core Components of Events”The modern event pattern in C# involves three main parts:
- The Delegate: This is a type that defines the signature of the event handler method. The standard convention is to use
System.EventHandleror the genericSystem.EventHandler<TEventArgs>. - The Event Data: A class that inherits from
System.EventArgs. It’s used to pass data from the publisher to the subscribers about the event. - The Event Itself: A member of the publisher class, declared with the
eventkeyword. This keyword ensures that subscribers can only add (+=) or remove (-=) handlers, not directly invoke the event.
A Modern Event Example: The Boiler
Section titled “A Modern Event Example: The Boiler”Let’s create a Boiler class that fires an event when its temperature gets dangerously high. We will follow modern C# best practices.
Step 1: Define the Event Data Class
Section titled “Step 1: Define the Event Data Class”// This class carries the information about the event.public class BoilerEventArgs : EventArgs{ public double Temperature { get; } public string Message { get; }
public BoilerEventArgs(double temperature, string message) { Temperature = temperature; Message = message; }}Step 2: Create the Publisher Class
Section titled “Step 2: Create the Publisher Class”public class Boiler{ // 1. Define the event using the generic EventHandler<T> delegate. // This states that any handler for this event must accept BoilerEventArgs. public event EventHandler<BoilerEventArgs>? BoilerAlert;
private double _currentTemperature;
public void SetTemperature(double temp) { _currentTemperature = temp; Console.WriteLine($"Boiler temperature set to: {_currentTemperature}°C");
// 2. Check the condition and raise the event if necessary. if (_currentTemperature > 95.0) { // Create the event arguments var args = new BoilerEventArgs(_currentTemperature, "DANGER: Boiler is overheating!");
// 3. Raise the event OnBoilerAlert(args); } }
// 4. A protected virtual method to raise the event (best practice). // This allows derived classes to customize the event-raising behavior. protected virtual void OnBoilerAlert(BoilerEventArgs e) { // The null-conditional operator (?.) ensures we only invoke the event // if there are subscribers, preventing a NullReferenceException. BoilerAlert?.Invoke(this, e); }}Step 3: Create the Subscriber and Handle the Event
Section titled “Step 3: Create the Subscriber and Handle the Event”// Subscriber classpublic class BoilerLogger{ // Event handler method. The signature must match the EventHandler<BoilerEventArgs> delegate. public void LogAlert(object? sender, BoilerEventArgs e) { Console.WriteLine($"LOG: Alert received. Temp: {e.Temperature}°C. Message: {e.Message}"); }}
// Another subscriberpublic class SafetySystem{ public void ShutdownBoiler(object? sender, BoilerEventArgs e) { Console.WriteLine($"SAFETY: Shutting down boiler immediately! Temp was {e.Temperature}°C."); }}
// --- Putting It All Together ---
// 1. Create instances of the publisher and subscribersvar boiler = new Boiler();var logger = new BoilerLogger();var safety = new SafetySystem();
// 2. Subscribe to the event. The methods are now event handlers.boiler.BoilerAlert += logger.LogAlert;boiler.BoilerAlert += safety.ShutdownBoiler;
// You can also subscribe using a lambda expression for concise handlersboiler.BoilerAlert += (sender, e) => { Console.WriteLine($"EMAIL: Notifying administrator about temperature {e.Temperature}°C.");};
// 3. Trigger the actions that will cause the event to be raisedConsole.WriteLine("--- Normal Operation ---");boiler.SetTemperature(80);
Console.WriteLine("\n--- DANGER --- ");boiler.SetTemperature(98);
// To unsubscribe:// boiler.BoilerAlert -= logger.LogAlert;When the above code is compiled and executed, it produces the following result:
--- Normal Operation ---Boiler temperature set to: 80°C
--- DANGER ---Boiler temperature set to: 98°CLOG: Alert received. Temp: 98°C. Message: DANGER: Boiler is overheating!SAFETY: Shutting down boiler immediately! Temp was 98°C.EMAIL: Notifying administrator about temperature 98°C.Real-World Applications
Section titled “Real-World Applications”- GUI Frameworks: In WPF, MAUI, or WinForms, UI elements like
Buttonpublish aClickevent. Your code subscribes to this event to perform an action. - ASP.NET Core: The application lifecycle has events like
ApplicationStartedthat services can hook into. - Asynchronous Programming: A background service can raise an event like
WorkCompletedto notify the main application thread that results are ready.
Common Pitfalls and Best Practices
Section titled “Common Pitfalls and Best Practices”- Memory Leaks: This is the most common problem. If a subscriber with a long lifetime (e.g., a static class) subscribes to an event from a publisher with a short lifetime, the publisher will not be garbage collected because the subscriber holds a reference to it. Always unsubscribe (
-=) from events when the subscriber is no longer needed, especially in classes that implementIDisposable. - Thread Safety: Event handlers are executed on the same thread that raises the event. If a background thread raises an event that updates the UI, you must dispatch the UI update back to the UI thread. This is a critical concept in GUI programming.
- Use
EventHandler<T>: Avoid creating custom delegate types. Stick to the standardEventHandler(for events with no data) andEventHandler<TEventArgs>for consistency. - Make Event Data Immutable: Properties on your
EventArgsclass should bereadonly({ get; }) to prevent subscribers from modifying the event data, which could cause unpredictable side effects for other subscribers.