第17章 言語に内蔵されたイベント機能連載 改訂版 C#入門(1/4 ページ)

C#のイベントは、あるクラスで発生した事象をほかのクラスに伝達する。この機能は、キーボードやマウスによるイベントの処理にもぴったりマッチする。

» 2002年12月11日 00時00分 公開
[川俣晶(http://www.autumn.org/)(株)ピーデー(http://www.piedey.co.jp/)]
連載 改訂版 C#入門
Insider.NET

 

「連載 改訂版 C#入門」のインデックス

連載目次

 本記事は、(株)技術評論社が発行する書籍『新プログラミング環境 C#がわかる+使える』から許可を得て転載したものです。同書籍に関する詳しい情報については、本記事の最後に掲載しています。

 「イベント」とは、あるクラスで発生した出来事をほかのクラスに伝達する機能である。例えば、ウィンドウ内のボタンがクリックされた、というような出来事を伝達するのに適した機能である。本章では、このイベント機能について解説する。

17-1 C#のイベント機能とは何か?

 C#のイベント機能は、あるクラスで発生した出来事を、あらかじめ登録された一群のメソッドすべてに対して、1回の呼び出しで伝える機能といえる。これはデリゲートによく似た機能である。事実、イベント機能はデリゲート機能を利用して実現されている。しかし、デリゲートとイベントは機能的に同等ではない。機能の相違は目的の相違からもたらされたものである。デリゲートは、任意のメソッド呼び出しを委譲するために使われるが、イベントはあるクラスで発生した出来事を外部に伝達するために使用される。頭の半分は、イベントはデリゲートに似たものだと思い、残りの半分は違うものだと思いながら本章を読んでいただきたい。

 まず、初めてのイベントとして、できるだけ小さく書いたサンプル・プログラムList 17-1を見てみよう。

  1: using System;
  2:
  3: namespace Sample001
  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:     [STAThread]
 14:     static void Main(string[] args)
 15:     {
 16:       Class1 target = new Class1();
 17:       target.sampleEvent += new SampleEventHandler(target.handler);
 18:       target.sampleEvent( target, EventArgs.Empty );
 19:     }
 20:   }
 21: }

List 17-1

 これを実行するとFig.17-1のようになる。

Fig.17-1

 List 17-1には、注目すべき点が5つある。第1にイベントで受け渡す引数の定義、第2にイベントそのもののインスタンスの定義、第3にイベント呼び出しの際に実行されるメソッドであるハンドラの定義、第4にハンドラをイベントに登録する手順、第5に実際にイベントを呼び出す手順である。注目すべき点が多いことに注意しよう。

 まず、5行目を見ていただきたい。デリゲートの機能を用いて、イベントで用いるメソッドの引数を定義している。

 次は8行目を見ていただきたい。ここでは、eventというキーワードが使われている。これがイベントの本体の定義である。eventキーワードを使って、メンバー変数を定義するようにイベントを定義する。SampleEventHandlerは、5行目のデリゲートの定義である。そして、sampleEventが、変数名のように機能する。ただし、イベントは、宣言しただけで利用可能になる。例えば、インスタンスをnewして、その結果を代入しておく、といった初期化コードは不要である。この点はデリゲートと異なる。イベント本体には2つの機能がある。それは、ハンドラの追加削除とイベントの発生である。ハンドラの追加は、17行目で行われている。イベントに対する+=演算子を使うと、ハンドラの追加となる(逆に-=ならハンドラの削除)。イベントの発生は、18行目のように、イベント名をメソッド名のように使って行う。

 ところで、ハンドラとは何か? それはイベントが呼び出されたときに実際に処理されるコードのことである。これは通常のメソッドとして記述する。9〜12行目が、ハンドラの例である。これがハンドラとして機能するのは、メソッドの戻り値と引数が、5行目で宣言したデリゲートと一致しているためである。

 プログラムの処理の流れを見てみよう。まず、16行目でClass1のインスタンスが作られる。そして、17行目でイベントにハンドラを登録している。SampleEventHandlerをnew演算子で生成しているのは、デリゲートのインスタンスを作っているのである。18行目では、登録されたハンドラを実際に呼び出している。その結果、11行目が実行され、コンソールにメッセージが表示されるのである。

 イベント呼び出し時の引数は、標準的な使い方では2個となる。最初の1個は、そのイベントを発生させたオブジェクトになる。2番目の引数は、ハンドラに引き渡す引数を指定する。ここでは引数を渡さないので、EventArgs.Emptyという値を指定している。これは、引数がないということを示すEventArgsクラスのEmptyプロパティである。

17-2 複数ハンドラを扱うイベント

 1回のイベント呼び出しで、複数のハンドラを呼び出すことができる。それを実際に行うサンプル・ソースをList 17-2に示す。ここでは、何かの処理があるとして、その前後に3つのインスタンスが前処理と後処理を必要としているというシチュエーションを想定した。実際には何も処理を行わないが、そこは想像力で補っていただきたい。

  1: using System;
  2:
  3: namespace Sample002
  4: {
  5:   public delegate void SampleEventHandler(object sender, EventArgs e);
  6:   class Class1
  7:   {
  8:     class Class2
  9:     {
 10:       public void startHandler( object o, EventArgs e )
 11:       {
 12:         Console.WriteLine("start handler called");
 13:       }
 14:       public void endHandler( object o, EventArgs e )
 15:       {
 16:         Console.WriteLine("end handler called");
 17:       }
 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:     [STAThread]
 38:     static void Main(string[] args)
 39:     {
 40:       Class1 main = new Class1();
 41:       main.startEvent(main,EventArgs.Empty);
 42:       main.process();
 43:       main.endEvent(main,EventArgs.Empty);
 44:     }
 45:   }
 46: }

List 17-2

 これを実行するとFig.17-2のようになる。

Fig.17-2

 ここでは3つのポイントがある。1つは、Class2クラスが2個のハンドラを持っていること。それぞれ、前処理用(10〜13行目)と後処理用(14〜17行目)である。2番目のポイントは、19〜20行目で分かるとおり、イベントが2個作られていること。もちろん、19行目が前処理用で、20行目が後処理用である。そして、最後のポイントは41行目と43行目である。41行目は1回だけイベントを呼び出している。同様に43行目でも1回だけイベントを呼び出している。しかし、その結果実行されるのは登録されたハンドラのすべて、ということになる。つまり、26〜31行目で登録されたハンドラがすべて実行されている。

 このように、ハンドラの有無や個数を意識することなく、イベント呼び出しを簡潔に記述できることがイベントの長所といえる。あるタイミングで処理を行いたいインスタンスが多数ある場合に、非常に便利である。

       1|2|3|4 次のページへ

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。