1. はじめに
では、C# の文脈で「イベント」(event)と言うとき、それは何かが起こったことを1つまたは複数のオブジェクトに安全に通知する仕組みを指します。イベントは魔法ではなく、内部的にはデリゲート(キーワード delegate)に対する誤用防止のための追加保護が付いたものです。
YouTube チャンネルの購読を想像してみてください。チャンネル(イベント所有オブジェクト)には「登録」ボタンがあり、視聴者(他のオブジェクト)はそれを押して購読者リスト(デリゲートのハンドラ)に入ります。作者が新しい動画を投稿すると(イベントを発火すると)、通知は購読者だけに届き、いつ通知するかはチャンネル所有者だけが決めます。重要な点:視聴者は購読や解除はできますが、通知を自分で発行することはできません — それはチャンネル所有者の権限です。
簡単な文法
// イベントハンドラの「形」を定義するデリゲートの宣言
public delegate void SimpleEventHandler();
// デリゲートに基づくイベントの宣言
public event SimpleEventHandler SomethingHappened;
シンプルです: event はイベントを宣言するキーワードで、イベントの型は常にデリゲートで指定されます。
2. 最初の簡単な例: 「ボタンが押された!」イベント
ステップ 1. イベントハンドラ用のデリゲートを定義する
// イベントハンドラ: 戻り値なし、パラメータなし
public delegate void ButtonClickHandler();
ステップ 2. イベントを持つクラス
public class Button
{
// イベント: 購読と解除はできるが、外部から直接発火はできない
public event ButtonClickHandler Click;
// 「ボタンを押す」メソッド
public void Press()
{
Console.WriteLine("ボタンが押されました!");
// イベントの呼び出し: 全ての購読者に通知
Click?.Invoke();
}
}
注意してください: イベントはデリゲートに基づいて宣言され、Press メソッド内で ?.Invoke() を使って呼び出しています。なぜかというと、イベントに購読者がいないと Click は null になる可能性があるからです。安全呼び出し演算子は、購読者がいる場合にのみハンドラが実行されることを保証します。
ステップ 3. イベントの購読とコードの実行
// 使用例
public class Program
{
public static void Main()
{
var button = new Button();
// リスナーを追加(イベントを購読)
button.Click += OnButtonClicked;
button.Click += () => Console.WriteLine("もう一つのハンドラ!");
button.Press(); // ボタンの押下をシミュレート
// イベントの購読を解除することもできる
button.Click -= OnButtonClicked;
button.Press();
}
// 普通のイベントハンドラ
public static void OnButtonClicked()
{
Console.WriteLine("ハンドラ: ボタンが押されました!");
}
}
最初の押下では両方のハンドラが実行され、2回目ではラムダだけが実行されます。
3. イベントとデリゲートの違い
デリゲートには自由に代入でき、購読リストを丸ごと置き換えることさえできます — 誰かが button.Click = null のように書けば、以前の購読がすべて消えてしまいます。
イベントは違います — オブジェクトへの結びつきがより厳密です。イベントを直接発火できるのは、そのイベントが宣言されているクラスの所有者だけです(例えばクラス内部からの Click())。他のコードは += で購読するか -= で解除することしかできず、一度に全部を「null にする」ことはできません。これがカプセル化を守り、購読システムを壊すことを防ぎます。
4. シグネチャ: イベント用デリゲートの型とパラメータ
.NET の慣習では、イベントハンドラは通常2つのパラメータを受け取ります — object sender(誰がイベントを発火したか)とイベント引数(EventArgs)。EventHandler や EventHandler<TEventArgs> を使うことが推奨されます。
例: パラメータ付きデリゲート
public delegate void ButtonClickHandler(object sender, EventArgs e);
EventArgs は追加情報を渡すための基底クラスです。もっと情報が必要なら、自分の派生クラスを作成します。
これをボタンに適用する
// イベント引数クラス
public class ButtonClickEventArgs : EventArgs
{
public string UserName { get; }
public ButtonClickEventArgs(string userName)
{
UserName = userName;
}
}
public class Button
{
public event EventHandler<ButtonClickEventArgs> Click;
public void Press(string userName)
{
Console.WriteLine("ボタンが押されました!");
Click?.Invoke(this, new ButtonClickEventArgs(userName));
}
}
public static void Main()
{
var button = new Button();
button.Click += OnButtonClicked;
button.Press("ヴァシリー");
}
public static void OnButtonClicked(object sender, ButtonClickEventArgs e)
{
Console.WriteLine($"ユーザー {e.UserName} がボタンを押しました!");
}
5. 図解: イベントの流れ
+-------------+
| ユーザー |
+-------------+
|
v
+--------------+
| Button.Press|
+--------------+
|
v
+-----------------+ +------------------------+
| Click を呼ぶ? |----->---| サブスクライバ 1 |
+-----------------+ +------------------------+
| | ハンドラを実行する |
v +------------------------+
+-----------------+ +------------------------+
| 次のサブスクライバ|--->-| サブスクライバ 2 |
+-----------------+ +------------------------+
: | ハンドラを実行する |
v +------------------------+
— Button.Press がイベントを呼び出し、購読者のハンドラが順番に実行されます。
6. 便利な注意点
.NET における推奨されるイベントの形
ライブラリやツールとの互換性を保つために、EventHandler と EventHandler<TEventArgs> を使いましょう。この方法だと、EventArgs に新しいプロパティを追加しても既存の購読者を壊さずに済みます。
ドキュメント: EventHandler, event.
イベント vs デリゲート
あるコンポーネントを特定のメソッドに結びつけたいだけならデリゲート(delegate)で十分です。プログラムの複数箇所がいつでも購読・解除できるようにしたいならイベント(event)を使いましょう。
7. イベントを作るときの典型的なミスと注意点
ミス #1: null チェックなしでイベントを呼ぶこと。 イベントに購読者がいないと、直接呼ぶと NullReferenceException になります。安全呼び出しを使いましょう:
Click?.Invoke(...);
ミス #2: クラスの外部からイベントを発火しようとすること。 イベントは宣言されたクラスの内部でのみ発火できます。別のクラスから呼ぼうとするとコンパイラがエラーを出します — これによりカプセル化が守られます。
ミス #3: 同じハンドラを複数回購読してしまうこと。 同じハンドラが複数回購読されていると、その回数だけ呼ばれます。これは購読機構の特徴なので、+= の重複や -= による正しい解除に注意してください。
GO TO FULL VERSION