C#のイベント機能とは何か?
C#のイベント機能は、あるクラスで発生した出来事を,あらかじめ登録された一群のメソッドに対して、1回の呼び出しによって、ですべて伝える機能と言える。と言っても、こんな説明ではさっぱり分からないという人が大半だと思うので、今回は実例を見ながら、1つずつイベント機能の使い方と、使う意義を探ってみたい。なお、イベントはデレゲートと呼ばれる機能を活用して使用するため、今回の説明の中には実はデレゲートを説明している部分がある。しかし、C#プログラミングでは、デレゲートを単体で使う機会よりも、イベントとして使う機会の方が遥かに多いのである。デレゲート単体を使いこなす能力は必ずしも要求されないが、イベントはウィンドウアプリケーションなどを記述すると絶対に避けては通れないのである。そのため、今回はイベントとして一括して扱った。より詳しいデレゲート単体の解説は、いずれこの連載で取り上げる予定である。
まず、初めてのイベントとして、できるだけ小さく書いたサンプル・プログラムを見てみよう
1: using System;
2:
3: namespace ConsoleApplication53
4: {
5: public delegate void SampleEventHandler(object sender, EventArgs e);
6: class Class1
7: {
8: public event SampleEventHandler sampleEvent;
9: public void handler( object o, EventArgs e )
10: {
11: Console.WriteLine("handler called");
12: }
13: static void Main(string[] args)
14: {
15: Class1 target = new Class1();
16: target.sampleEvent += new SampleEventHandler(target.handler);
17: target.sampleEvent( target, EventArgs.Empty );
18: }
19: }
20: } |
|
イベントを使用した小さなサンプル・プログラム1 |
このプログラムには注目すべき点が5つある。 |
これを実行すると以下のようになる。
 |
サンプル・プログラム1の実行結果 |
最終的には、イベントのハンドラであるhandlerメソッドが呼び出され、メッセージが表示される。
|
このサンプルソースには、注目すべき点が5つある。第1に、イベントで受け渡す引数の定義。第2に、イベントそのもののインスタンスの定義。第3に、イベント呼び出しの際に実行されるメソッドであるハンドラの定義。第4に、ハンドラをイベントに登録する手順。第5に、実際にイベントを呼び出す手順である。注目すべき点が多いことに注意しよう。
まず、5行目を見ていただきたい。これは、デレゲート(delegate)と呼ばれる機能を用いて、イベントで用いるメソッドの引数を定義している。デレゲートについては、いずれ詳しく説明したい。ここでは、delegateキーワードを付けて、クラスの外側で中身のないメソッドを宣言するように記述すると理解してほしい。これは一種のデータ型の定義として機能する点に注意していただきたい。つまり、SampleEventHandlerはデータ型の名前になり、この名前でメソッド呼び出しなどを行うことはできない。
5: public delegate void SampleEventHandler(object sender, EventArgs e); |
さて、次は8行目を見ていただきたい。ここでは、eventというキーワードが使われている。これがイベントの本体の定義である。eventキーワードを使って、メンバ変数を定義するように、イベントを定義する。SampleEventHandlerは、5行目の定義をデータ型のように使っているものだ。そして、sampleEventが、変数名のように機能する。ただし、イベントは、宣言しただけで利用可能になる。例えば、インスタンスをnewして、その結果を代入しておく、といった初期化コードは不要である。ただ、定義するだけですぐに使える。
8: public event SampleEventHandler sampleEvent; |
イベント本体には2つの機能がある。それはハンドラの追加/削除と、イベントの発生である。ハンドラの追加は、16行目で行われている。イベントに対する+=演算子を使うと、ハンドラの追加となる(逆に-=なら、ハンドラの削除になる)。イベントの発生は、17行目のように、イベント名をメソッド名のように使って行う。
15: Class1 target = new Class1();
16: target.sampleEvent += new SampleEventHandler(target.handler);
17: target.sampleEvent( target, EventArgs.Empty );
|
さて、ハンドラとは何か。それはイベントが呼び出されたときに実際に処理されるコードのことである。これは通常のメソッドとして記述する。9~12行目が、ハンドラの例である。これがハンドラとして機能するのは、メソッドの戻り値と引数が、5行目で宣言したデレゲートと一致しているためである。
9: public void handler( object o, EventArgs e )
10: {
11: Console.WriteLine("handler called");
12: }
|
プログラムの処理の流れを見てみよう。まず、15行目でClass1のインスタンスが作られる。そして、16行目で、イベントにハンドラを登録している。SampleEventHandlerをnew演算子で生成しているのは、デレゲートのインスタンスを作っているのである。17行目は、登録されたハンドラを実際に呼び出している。その結果、11行目が実行され、コンソールにメッセージが表示されるのである。
イベント呼び出し時の引数は、標準的な使い方では2個となる。最初の1個は、そのイベントを発生させたオブジェクトになる。2番目の引数としては、ハンドラに渡す引数を指定する。ここでは引数を渡さないので、EventArgs.Emptyという値を指定している。これは、引数がないと言うことを示すEventArgsクラスのEmptyプロパティである。
複数ハンドラを扱うイベント
さて、上記のサンプルソースのコードの動作が理解できたとしても、いったい何の役に立つやらと思った人も多いと思う。呼び出したいメソッドがあれば、回りくどいことをせず、直接呼び出せばよい。だが、イベントはただ単に1つのメソッドを呼び出すだけのものではない。たった1回のメソッド呼び出しが複数のハンドラを呼び出す例を以下に示そう。このプログラムでは、何かの処理があるとして、その前後に3つのインスタンスが前処理と後処理を必要としている、というシチュエーションを想定した。実際には何も処理を行わないが、そこは想像力で補っていただきたい。
1: using System;
2:
3: namespace ConsoleApplication54
4: {
5: public delegate void SampleEventHandler(object sender, EventArgs e);
6: class Class2
7: {
8: public void startHandler( object o, EventArgs e )
9: {
10: Console.WriteLine("start handler called");
11: }
12: public void endHandler( object o, EventArgs e )
13: {
14: Console.WriteLine("end handler called");
15: }
16: }
17: class Class1
18: {
19: public event SampleEventHandler startEvent;
20: public event SampleEventHandler endEvent;
21: Class2 target1 = new Class2();
22: Class2 target2 = new Class2();
23: Class2 target3 = new Class2();
24: public Class1()
25: {
26: startEvent += new SampleEventHandler(target1.startHandler);
27: endEvent += new SampleEventHandler(target1.endHandler);
28: startEvent += new SampleEventHandler(target2.startHandler);
29: endEvent += new SampleEventHandler(target2.endHandler);
30: startEvent += new SampleEventHandler(target3.startHandler);
31: endEvent += new SampleEventHandler(target3.endHandler);
32: }
33: public void process()
34: {
35: // 何かの処理があると思ってください
36: }
37: static void Main(string[] args)
38: {
39: Class1 main = new Class1();
40: main.startEvent(main,EventArgs.Empty);
41: main.process();
42: main.endEvent(main,EventArgs.Empty);
43: }
44: }
45: } |
|
複数ハンドラを扱うイベントを使用したサンプル・プログラム2 |
1つのイベントに複数のハンドラを登録することができる。 |
これを実行すると以下のようになる。
 |
サンプル・プログラム2の実行結果 |
イベントを呼び出すと、それに登録されているすべてのハンドラが呼び出される。
|
ここでは3つのポイントがある。1つは、Class2クラスが2個のハンドラを持っていること。それぞれ、前処理用(8~11行目)と、後処理用(12~15行目)である。2番目のポイントは、19~20行目で分かるとおり、イベントが2個作られていること。もちろん、19行目が前処理用で、20行目が後処理用である。そして、最後のポイントは40行目と、42行目である。40行目は1回だけイベントを呼び出している。同様に42行目でも、1回だけイベントを呼び出している。しかし、その結果実行されるのは登録されたハンドラのすべて、ということになる。つまり、26~31行目で登録されたハンドラがすべて実行されている。
このように、ハンドラの有無や個数を意識することなく、イベント呼び出しを簡潔に記述できることがイベントの長所と言える。あるタイミングで処理を行いたいインスタンスが多数ある場合に、非常に便利である。
Insider.NET 記事ランキング
本日
月間