CodeGym /コース /C# SELF /「パブリッシャー-サブスクライバー」パターンとC#のイベント ( event)

「パブリッシャー-サブスクライバー」パターンとC#のイベント ( event)

C# SELF
レベル 52, レッスン 0
使用可能

1. はじめに

プログラムを扱うと、ある部分が他の部分に「何か重要なことが起きた」と通知しなければならない状況に必ず直面します。典型的な例は、ユーザーがマウスをクリックして、そのイベントを処理する必要がある場合です。日常生活でもイベントの世界に生きています:キッチンでやかんが鳴ったらシグナルを聞いてコンロを消す。コーヒーがキーボードにこぼれたら心臓がドキッとしてノートパソコンを救おうとする。プログラミングも同じ法則に従います。

イベントとは、ソースオブジェクト(パブリッシャー)が起きた変更やアクションについて他のオブジェクト(サブスクライバー)に知らせる仕組みです。いわば「知らせたら、聞いていた人が反応する」という感じです。

C#ではイベントはデリゲート型に基づく特別な構文です。デリゲートはコールバックのシグネチャを定義します—サブスクライバー側で何がどのように呼ばれるかを決めます。イベントの宣言はキーワード event、デリゲートはキーワード delegate で行います。

なぜイベントが必要なのか?

  • 疎結合:パブリッシャーはサブスクライバーについて何も知らない—ただ信号を送るだけ。
  • アーキテクチャの柔軟性:パブリッシャーのコードを変えずにハンドラを動的に追加・削除できる。
  • スケーラビリティ:新しいサブスクライバーを追加すれば、すぐに通知を受け取れる。

古典的な「パブリッシャー-サブスクライバー」パターン

「火災報知器」クラス(パブリッシャー)と「建物の人」クラス(サブスクライバー)があると想像してみてください。アラームが鳴ると同時に全員に信号を送ります—建物に何人いてどこにいるかは関係ありません。これが「パブリッシャー-サブスクライバー」(またはObserver)パターンです。

パブリッシャーは何人のどんなサブスクライバーがいるかを知らない—ただ通知するだけで、残りは自分で購読するか無視するか決めます。

C#ではどう動くのか?

  • パブリッシャー:イベント(event)を定義し、購読/解除を提供する。
  • サブスクライバー:イベントに購読し、ハンドラを実装する(イベント発生時に呼ばれるメソッド)。

2. 実践:最初の例

家からコードに移って、最も単純なモデルを説明します。コンソールアプリケーションがあって、タイマーオブジェクトが毎秒「ティック」して、いくつかのハンドラが反応する(例えばコンソールに「ティック」を表示したり、ティック数を数えたり)とします。

ステップ1. デリゲートとイベントの定義


public class SimpleTimer
{
    // イベント用のデリゲートを宣言する
    public delegate void TickEventHandler(object sender, EventArgs e);

    // デリゲートに基づくイベント
    public event TickEventHandler Tick;

    public void Start(int count)
    {
        for (int i = 0; i < count; i++)
        {
            System.Threading.Thread.Sleep(1000); // ティックの模擬!
            OnTick(); // イベントを発火!
        }
    }

    protected virtual void OnTick()
    {
        // 購読者がいるならイベントを呼び出す (Tick != null)
        Tick?.Invoke(this, EventArgs.Empty);
    }
}

ここで何が起きているか?

  • 古典的なシグネチャである object sender, EventArgs e を持つデリゲート TickEventHandler を定義している。
  • イベント Tick はハンドラの購読ポイント。
  • Start メソッドは「ティック」を模擬し、タイマーごとに OnTick を呼び出す。
  • OnTick では安全にイベントを呼ぶ:Tick?.Invoke(..., EventArgs.Empty)。

ステップ2. イベントの購読


class Program
{
    static void Main()
    {
        var timer = new SimpleTimer();

        // Tick イベントに購読する
        timer.Tick += Timer_Tick;

        timer.Start(3);

        // 必要なら解除もできる
        timer.Tick -= Timer_Tick;
    }

    static void Timer_Tick(object sender, EventArgs e)
    {
        Console.WriteLine("ティック!");
    }
}

タイマーを作って、演算子 += で購読し、ティックごとにハンドラが呼ばれる。解除は演算子 -= を使う。

3. 役に立つニュアンス

なぜイベントは「ハードコール」より良いのか?

もし SimpleTimer が OnTick の中で直接コンソールに書き込んでいたら、そのクラスは特定のアクションに強く結合されてしまう。イベントを使えばコードは解放される:タイマーはサブスクライバーが何をするか知らない—ロケットを発射するか、ファイルにログを書くか、メールを送るかは関係ない。

イベントとデリゲートの重要な違い

  • デリゲートはメソッドへの「ポインタ」、一方で イベント はアクセス制限のあるデリゲート。
  • サブスクライバーは購読/解除しかできない;イベントを外部から呼ぶことはできない—それはパブリッシャーだけができる。
  • イベントを宣言するにはデリゲート型に event 修飾子を付ける—コンパイラが適切なアクセスモデルを提供する。

C#におけるイベントの簡単な動作図


+------------------+          +------------------------------+
|                  |          |                              |
|    パブリッシャー    | <------> |        サブスクライバー          |
| (Publisher/Event)|          |      (Subscriber/Handler)    |
|                  |          |                              |
+------------------+          +------------------------------+
         | 1) イベントを宣言         | 2) それを購読する
         | 3) イベントを発火         | 4) ハンドラを実装する

いつイベントを使うべきか?

  • 不特定多数のリスナーに何かが起きたことを知らせたいとき。
  • ソースクラス内でアクションのロジックを結び付けたくないとき。
  • UI、非同期インタラクション、システム通知—あらゆるところでイベントが使われる。

C#でのイベント実装に関する短いポイント

  • イベントは外部から呼べない:呼べるのはパブリッシャーのコードだけで、Invoke を使う。
  • 購読/解除:演算子 +=/-=;ハンドラは複数登録できる。
  • イベントは実際はデリゲートのリスト:イベントが起きると、登録順に全てのハンドラが呼ばれる。
  • 推奨されるデリゲート:互換性のために EventHandler と EventHandler<TEventArgs> を使うことを推奨。

4. 初心者がやりがちな典型的なミス

購読者がいるかどうかをチェックするのを忘れる:Tick != null。安全な呼び出し(Tick?.Invoke(...))を使う方が良い。

イベントに購読したまま、ハンドラが不要になったときに解除しない。これによりオブジェクトがメモリに残り、メモリリークにつながることがある。

外部クラスからイベントを「呼ぼう」とする—コンパイラは許可しない。イベントがメソッドでない場合、たとえば game.GameOver() のようには書けない。

デリゲートのシグネチャを守らない。イベントには標準の EventHandler や EventHandler<TEventArgs> を使うと、他の .NET ライブラリと互換性が保てる。

2
タスク
C# SELF, レベル 52, レッスン 0
ロック未解除
イベントとサブスクライバーの作成
イベントとサブスクライバーの作成
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION