C# のイベント処理の event キーワードの働き
イベントの実装に event キーワードを付けない場合
前の記事「C# のイベントの実装方法」で、イベントを実装するにはデリゲートに event キーワードをつけて実装するということを説明しました。
ところが実際には、 event キーワードがなくてもデリゲートは動作します。イベントハンドラがコールバックされる、という点では問題がありません。
では、なぜ event キーワードを付ける必要があるのでしょうか。event キーワードを付けていない次のサンプルコードを使って確認してみましょう。
using System;
using static System.Console;
class MyEventArgs
{
public int x { get; set; }
public int y { get; set; }
public MyEventArgs(int x, int y)
{
this.x = x;
this.y = y;
}
}
class MyButton
{
public EventHandler<MyEventArgs> ClickHandler;
public void Click()
{
// ダミーデータ
var r = new Random();
var x = r.Next(100);
var y = r.Next(100);
if (ClickHandler != null)
{
var args = new MyEventArgs(x, y);
ClickHandler(this, args);
}
}
}
class Program
{
static void Main(string[] args)
{
var button = new MyButton();
button.ClickHandler = OnClick;
button.Click();
}
static void OnClick(object sender, MyEventArgs args)
{
WriteLine($"OnClick: ({args.x}, {args.y})");
}
}
これを実行すると、次のような出力が確認できると思います。
dotnet run
OnClick: (5, 23)
確かにイベントハンドラは呼び出されています。
コールバックできるかという点だけで言えば、これで要件を満たしています。しかし、下の理由からイベントとして実装するには event キーワードを付けるのが良いです。
イベントハンドラの設定時に += が必要となる
event キーワードがない場合は、 上記のコードにあるようにイベントハンドラの設定 (40行目) で、 = 演算子を用いることができます。
var button = new MyButton();
button.ClickHandler = OnClick;
button.Click();
しかし、イベント処理は「出版/購読型」のパターン (Publish/Subscribe pattern) を実装するものなので、 Publisher が 1 に対して Subscriber は多数となるのが基本です。
そのため、イベントハンドラを設定する箇所は = ではなく、 イベントハンドラの追加の意味の += とすべきところです。
event キーワードを付ければ、 += としないとコンパイル時にエラーとなります。 これによって、イベントハンドラのインボケーションリストをある場所で全て上書きしてしまうなどの不具合の検出が容易になります。
デリゲートの直接の呼び出しを抑制する
event キーワードを付けていないデリゲートは、直接呼び出すことが可能です。
例えば下の 7 行目のようにデリゲートを直接呼び出せます。
class Program
{
static void Main(string[] args)
{
var button = new MyButton();
button.ClickHandler += OnClick;
button.ClickHandler(null, new MyEventArgs(-1, -2));
}
static void OnClick(object sender, MyEventArgs args)
{
WriteLine($"OnClick: ({args.x}, {args.y})");
}
}
ここでは一つしかイベントハンドラは登録されていませんが、もしデリゲートのインボケーションリストに複数のメソッドが登録されていれば、 それらは全て呼び出されてしまいます。
つまり、本来 Publisher (イベントの発行者) ではないオブジェクトが、勝手にイベントハンドラを呼び出してしまうことが可能になっています。
event キーワードを付けると、このような直接呼び出しは禁止され、コンパイル時のエラーとして直ちに検出されます。