1. イベント購読の構文
タスク管理アプリを想像してみてください: タスクが完了するとイベントが発火して、ハンドラが通知を送ったり、UIを更新したり、ログを書き込んだりできます。C#ではイベントへのサブスクライブが扱いやすく、安全に行えます。特定のハンドラが特定のイベントに反応することを明示できます。
C#でのイベント購読は、パーティのゲストリストに自分の名前を追加するようなものです:
publisher.MyEvent += HandlerMethod;
ここで publisher はイベント MyEvent を宣言したオブジェクトで、HandlerMethod はそのイベントが発生したときに呼ばれるメソッドです。
ミニマルな例で見てみましょう。クリック数を数えるアプリがあるとします:
public class Clicker
{
public event Action Clicked;
public void Click()
{
// 何かがクリックされた!
Clicked?.Invoke();
}
}
こちらがイベント Clicked を公開する「パブリッシャー」です。さあ、ハンドラを登録してみましょう:
Clicker clicker = new Clicker();
void OnClicked()
{
Console.WriteLine("ボタンが押されました!");
}
clicker.Clicked += OnClicked;
// どこかで
clicker.Click();
// → "ボタンが押されました!"
どう動くかというと、クリックイベントに対してメソッド OnClicked を追加しており、クリックが発生するたびに呼ばれます。
2. イベントハンドラ: 種類と宣言方法
イベントハンドラは、イベントが発生したときに呼ばれるメソッドです。ハンドラのシグネチャはイベントのデリゲート型と一致している必要があります。例えば、イベントが public event Action Clicked; のように宣言されているなら、ハンドラは引数なし、戻り値なしのメソッドである必要があります。
Action 用のハンドラ
void OnClicked()
{
Console.WriteLine("イベントが発生しました (Action)!");
}
標準の EventHandler 用のハンドラ
古典的な EventHandler を使う場合、ハンドラは2つのパラメータを受け取ります: 送信者 (object sender) とイベントデータ (EventArgs e)。
public class Alarm
{
public event EventHandler AlarmRaised;
public void RaiseAlarm()
{
AlarmRaised?.Invoke(this, EventArgs.Empty);
}
}
Alarm alarm = new Alarm();
void AlarmHandler(object sender, EventArgs e)
{
Console.WriteLine("アラームが作動しました!");
}
alarm.AlarmRaised += AlarmHandler;
alarm.RaiseAlarm();
匿名メソッドとラムダ式
C#では匿名関数やラムダ式を直接サブスクライブ時に使えます:
clicker.Clicked += () => Console.WriteLine("Another click!");
引数付きのイベントなら少し複雑なラムダも:
alarm.AlarmRaised += (sender, e) =>
{
Console.WriteLine($"Alarm raised by: {sender}");
};
4. 役に立つ細かい点
サブスクライブとアンサブスクライブ: 重要なポイント
現実ではゲストが多すぎたり、帰りたくなったりします。デリゲートでも同じで、ハンドラは追加(サブスクライブ)も削除(アンサブスクライブ)もできます:
// サブスクライブ
publisher.MyEvent += MyHandler;
// アンサブスクライブ(ハンドラが不要になったとき)
publisher.MyEvent -= MyHandler;
なぜ大事かというと、特に大きなアプリではアンサブスクライブしないとハンドラが「ぶら下がったまま」になり、ガベージコレクタがオブジェクトを回収できず、メモリリークにつながるからです。
複数のハンドラ
1つのイベントに対して好きなだけハンドラを登録できます。登録した順に呼ばれます。
clicker.Clicked += () => Console.WriteLine("最初のハンドラ!");
clicker.Clicked += () => Console.WriteLine("二番目のハンドラ!");
clicker.Click();
// → 最初のハンドラ!
// → 二番目のハンドラ!
その場でハンドラを登録・解除することもできて、柔軟です。
本当に重要な理由: 実際のシナリオ
- UI (Windows Forms, WPF, WinUI, MAUI): ボタンのクリックやテキストフィールドの変更はイベントです。
- FileSystemWatcher: フォルダに新しいファイルが出現したことを通知します。
- 非同期操作: ファイルのダウンロード完了や進行状況のイベント。
- プラグインシステム: 個別のモジュールがメインアプリのイベントにサブスクライブします。
イベントモデルは拡張可能なアーキテクチャを作るのに向いています。既存のイベントにモジュールを追加してサブスクライブするだけで、基礎コードを変えずに機能を拡張できます。
サブスクライブの例
| イベントのシグネチャ | サブスクライブ例 | ハンドラ例 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
5. よくあるミスと落とし穴
ミスその1: ハンドラのシグネチャ不一致。
イベントが event Action<int> として宣言されているのに、引数なしのメソッドを登録しようとするとコンパイラエラーになります。ハンドラがイベントの要求するシグネチャに合っているか常に確認しましょう。
ミスその2: ラムダでの変数キャプチャ。
ラムダでサブスクライブすると、周囲のスコープの変数をキャプチャします(「クロージャ」)。サブスクライブ後にその変数が変わると、ハンドラは新しい値を参照するため、予期せぬ動作になることがあります。
ミスその3: 初期化されていないオブジェクトのイベントにサブスクライブすること。
イベントを含むオブジェクトが作成・初期化される前にサブスクライブすると、NullReferenceException を招くリスクがあります。サブスクライブ前にオブジェクトが準備できているか確認してください。
ミスその4: 同じハンドラを複数回サブスクライブしてしまうこと。
同じハンドラが複数回登録されていると、その分だけ複数回呼ばれます。仕様としては正常ですが、多くの場合驚きの原因になります。
GO TO FULL VERSION