【C#】自作イベントの定義とEventHandlerを利用したデータ通知

C#における「イベント」は、クラス間で疎結合な通知機能を実現するための重要な機構です。ボタンクリックなどのGUIイベントだけでなく、ビジネスロジックにおいても「状態の変化」や「閾値の超過」などを外部に通知する際に多用されます。

かつては独自のデリゲートを定義してイベントを実装していましたが、現在の.NET開発ではジェネリックなEventHandler<TEventArgs>を使用するのが標準的です。ここでは、温度センサーが異常値を検知した際に通知を行う監視システムを題材に、自作イベントの定義と利用方法について解説します。


目次

イベントパターンの基本構成

.NETにおける標準的なイベント実装は、以下の3つの要素で構成されます。

  1. EventArgsの継承クラス: 通知したいデータを格納するクラス。
  2. イベントの宣言: EventHandler<T>型を使用したイベントメンバ。
  3. イベント起動メソッド: イベントを安全に発行(Invoke)するためのOnEventNameメソッド。

実践的なコード例:温度監視センサー

以下のコードは、温度が特定の閾値(30度)を超えた場合に、その温度と発生時刻をイベントとして通知するクラスの実装例です。

using System;

namespace SensorSystem
{
    // 1. イベントで渡すデータを定義するクラス
    // System.EventArgs を継承するのが作法です。
    public class TemperatureAlertEventArgs : EventArgs
    {
        public double Temperature { get; }
        public DateTime Timestamp { get; }

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

    // 2. イベントを発行するクラス(Publisher)
    public class TemperatureSensor
    {
        // 外部に公開するイベント
        // EventHandler<T> を使用することで、独自デリゲートの定義が不要になります。
        public event EventHandler<TemperatureAlertEventArgs> AlertTriggered;

        // イベントを安全に発行するためのメソッド
        // 派生クラスでオーバーライドできるよう、protected virtual にするのが一般的です。
        protected virtual void OnAlertTriggered(TemperatureAlertEventArgs e)
        {
            // Null条件演算子 (?.) を使用して、購読者がいる場合のみ実行します。
            // これにより、購読者が0人の場合の NullReferenceException を回避できます。
            AlertTriggered?.Invoke(this, e);
        }

        // 温度を更新するメソッド(シミュレーション用)
        public void UpdateTemperature(double newTemperature)
        {
            Console.WriteLine($"現在温度: {newTemperature:F1}°C");

            // 閾値(30度)を超えた場合にイベントを発火
            if (newTemperature > 30.0)
            {
                var args = new TemperatureAlertEventArgs(newTemperature);
                OnAlertTriggered(args);
            }
        }
    }

    // 3. イベントを利用するクラス(Subscriber)
    class Program
    {
        static void Main()
        {
            var sensor = new TemperatureSensor();

            // イベントハンドラの登録(購読)
            // += 演算子を使用します。
            sensor.AlertTriggered += OnSensorAlert;

            // 温度変化のシミュレーション
            sensor.UpdateTemperature(25.0); // 正常
            sensor.UpdateTemperature(28.5); // 正常
            sensor.UpdateTemperature(31.2); // 警告イベント発生!
            sensor.UpdateTemperature(29.0); // 正常
            sensor.UpdateTemperature(34.8); // 警告イベント発生!

            // イベントハンドラの解除
            // 不要になったら -= で解除することをお勧めします(メモリリーク防止)。
            sensor.AlertTriggered -= OnSensorAlert;
        }

        // イベント発生時に呼び出されるメソッド
        // シグネチャは (object sender, TEventArgs e) に合わせます。
        static void OnSensorAlert(object sender, TemperatureAlertEventArgs e)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"[警報] 異常温度を検知しました!");
            Console.WriteLine($"  - 温度: {e.Temperature:F1}°C");
            Console.WriteLine($"  - 時刻: {e.Timestamp}");
            Console.ResetColor();
        }
    }
}

実行結果

現在温度: 25.0°C
現在温度: 28.5°C
現在温度: 31.2°C
[警報] 異常温度を検知しました!
  - 温度: 31.2°C
  - 時刻: 2025/12/06 14:00:00
現在温度: 29.0°C
現在温度: 34.8°C
[警報] 異常温度を検知しました!
  - 温度: 34.8°C
  - 時刻: 2025/12/06 14:00:02

技術的なポイントと解説

1. EventHandler<T>の利用

C# 1.xの時代は、イベントごとに delegate void MyEventHandler(...) のようなデリゲート型を定義する必要がありました。しかし、ジェネリクスが導入されて以降は、標準ライブラリに含まれる System.EventHandler<TEventArgs> を使用することが推奨されています。これにより、定義の手間が省け、コードの統一性が向上します。

2. イベント発行時のスレッドセーフ

イベントを発行する際、AlertTriggered?.Invoke(this, e) という記述を使用しています。 これは以下のコードの短縮形であり、かつスレッドセーフです。

// 古い記述(スレッド競合のリスクがある)
if (AlertTriggered != null)
{
    AlertTriggered(this, e);
}

Null条件演算子(?.)を使用することで、nullチェックと呼び出しをアトミック(不可分)に行えるため、マルチスレッド環境で「nullチェック直後にイベントが解除されてnullになる」という競合状態を防ぐことができます。

3. senderとEventArgs

イベントハンドラの引数は、以下の慣習に従います。

  • sender (object): イベントの発生元オブジェクト(通常は this を渡します)。
  • e (TEventArgs): イベントに関する詳細データ。データが不要な場合は EventArgs.Empty を渡します。

まとめ

自作クラスにイベントを実装することで、クラス間の結合度を下げ、Observerパターンを容易に実現できます。「状態が変わったときに、誰かがそれに反応できるようにしたい」という設計が必要な場合は、EventHandler<T>を使用したイベント定義を第一の選択肢として検討してください。

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

この記事を書いた人

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

目次