[C#] Defining Custom Events and Notifying Data Using EventHandler

“Events” in C# are an important mechanism for achieving loose coupling between classes. They are frequently used not only for GUI events like button clicks but also in business logic to notify external parts about “state changes” or “threshold utilization.”

In the past, developers had to define their own delegates to implement events. However, in modern .NET development, using the generic EventHandler<TEventArgs> is the standard approach.

This article explains how to define and use custom events, using a monitoring system where a temperature sensor notifies users when it detects abnormal values as an example.

目次

Basic Structure of the Event Pattern

A standard event implementation in .NET consists of the following three elements:

  1. EventArgs Derived Class: A class that holds the data you want to notify.
  2. Event Declaration: An event member using the EventHandler<T> type.
  3. Event Invocation Method: An OnEventName method to safely publish (Invoke) the event.

Practical Code Example: Temperature Monitoring Sensor

The following code is an implementation example of a class that notifies the temperature and the time of occurrence as an event when the temperature exceeds a specific threshold (30 degrees).

using System;

namespace SensorSystem
{
    // 1. Class defining data passed by the event
    // It is standard practice to inherit from System.EventArgs.
    public class TemperatureAlertEventArgs : EventArgs
    {
        public double Temperature { get; }
        public DateTime Timestamp { get; }

        public TemperatureAlertEventArgs(double temperature)
        {
            Temperature = temperature;
            Timestamp = DateTime.Now;
        }
    }

    // 2. Class that publishes the event (Publisher)
    public class TemperatureSensor
    {
        // Event exposed externally
        // Using EventHandler<T> eliminates the need to define a custom delegate.
        public event EventHandler<TemperatureAlertEventArgs> AlertTriggered;

        // Method to safely invoke the event
        // It is common to make this protected virtual so derived classes can override it.
        protected virtual void OnAlertTriggered(TemperatureAlertEventArgs e)
        {
            // Use the null-conditional operator (?.) to execute only if there are subscribers.
            // This avoids a NullReferenceException if there are zero subscribers.
            AlertTriggered?.Invoke(this, e);
        }

        // Method to update temperature (for simulation)
        public void UpdateTemperature(double newTemperature)
        {
            Console.WriteLine($"Current Temp: {newTemperature:F1}°C");

            // Fire event if threshold (30 degrees) is exceeded
            if (newTemperature > 30.0)
            {
                var args = new TemperatureAlertEventArgs(newTemperature);
                OnAlertTriggered(args);
            }
        }
    }

    // 3. Class that uses the event (Subscriber)
    class Program
    {
        static void Main()
        {
            var sensor = new TemperatureSensor();

            // Register event handler (Subscribe)
            // Use the += operator.
            sensor.AlertTriggered += OnSensorAlert;

            // Simulate temperature changes
            sensor.UpdateTemperature(25.0); // Normal
            sensor.UpdateTemperature(28.5); // Normal
            sensor.UpdateTemperature(31.2); // Alert Event Triggered!
            sensor.UpdateTemperature(29.0); // Normal
            sensor.UpdateTemperature(34.8); // Alert Event Triggered!

            // Unregister event handler
            // It is recommended to unregister with -= when no longer needed (to prevent memory leaks).
            sensor.AlertTriggered -= OnSensorAlert;
        }

        // Method called when the event occurs
        // The signature matches (object sender, TEventArgs e).
        static void OnSensorAlert(object sender, TemperatureAlertEventArgs e)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"[Main] Abnormal temperature detected!");
            Console.WriteLine($"  - Temp: {e.Temperature:F1}°C");
            Console.WriteLine($"  - Time: {e.Timestamp}");
            Console.ResetColor();
        }
    }
}

Execution Result

Current Temp: 25.0°C
Current Temp: 28.5°C
Current Temp: 31.2°C
[Main] Abnormal temperature detected!
  - Temp: 31.2°C
  - Time: 2025/12/06 14:00:00
Current Temp: 29.0°C
Current Temp: 34.8°C
[Main] Abnormal temperature detected!
  - Temp: 34.8°C
  - Time: 2025/12/06 14:00:02

Technical Points and Explanation

1. Using EventHandler<T>

In the era of C# 1.x, it was necessary to define a delegate type like delegate void MyEventHandler(...) for each event. However, since the introduction of Generics, it is recommended to use System.EventHandler<TEventArgs> included in the standard library. This saves the effort of definition and improves code consistency.

2. Thread Safety When Publishing Events

When publishing an event, we used the syntax AlertTriggered?.Invoke(this, e). This is a shorthand for the following code and is thread-safe:

// Old syntax (Risk of thread race condition)
if (AlertTriggered != null)
{
    AlertTriggered(this, e);
}

By using the Null-conditional operator (?.), the null check and the invocation are performed atomically. This prevents race conditions in a multi-threaded environment where “the event is unsubscribed and becomes null immediately after the null check.”

3. sender and EventArgs

Event handler arguments follow these conventions:

  • sender (object): The object that originated the event (usually pass this).
  • e (TEventArgs): Detailed data regarding the event. If no data is needed, pass EventArgs.Empty.

Summary

Implementing events in your custom classes allows you to reduce coupling between classes and easily realize the Observer pattern. If your design requires “letting someone react when a state changes,” consider defining events using EventHandler<T> as your primary choice.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次