連載
» 2002年12月11日 00時00分 公開

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

[川俣晶(http://www.autumn.org/),(株)ピーデー(http://www.piedey.co.jp/)]

17-6 独自のハンドラ管理

 通常、イベントへのハンドラの追加と削除は、システム側がお膳立てしていてくれるため、自分でコードを書く必要もなければ、詳細を知る必要もない。しかし、ハンドラの管理をプログラマーが自分で記述することもできる。List 17-7が、その方法を示した例である。

  1: using System;
  2: using System.ComponentModel;
  3:
  4: namespace Sample007
  5: {
  6:   public delegate void SampleEventHandler(object sender, EventArgs e);
  7:   class Class1
  8:   {
  9:     public static event SampleEventHandler sampleEvent
 10:     {
 11:       add
 12:       {
 13:         Console.WriteLine("add called");
 14:       }
 15:       remove
 16:       {
 17:         Console.WriteLine("remove called");
 18:       }
 19:     }
 20:     public static void handler( object o, EventArgs e )
 21:     {
 22:       Console.WriteLine("handler called");
 23:     }
 24:     [STAThread]
 25:     static void Main(string[] args)
 26:     {
 27:       Console.WriteLine("add event handler");
 28:       sampleEvent += new SampleEventHandler(handler);
 29:       Console.WriteLine("remove event handler");
 30:       sampleEvent -= new SampleEventHandler(handler);
 31:       Console.WriteLine("done");
 32:     }
 33:   }
 34: }

List 17-7

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

Fig.17-5

 ここでポイントになるのは、11行目のaddキーワードと15行目のremoveキーワードである。イベントの後に中括弧({})でブロックを作り、その中にプロパティでgetとsetを記述するのと同様に、addとremoveを記述することができる。addは、ハンドラの追加時に呼び出される。removeはハンドラの削除時に呼び出される。そのため、その内部に自分でハンドラを管理するコードを記述すれば、それでイベントの独自管理が実現できる。とはいえ、めったに使うことのない機能だろう。忘れていても、それほど実害はなさそうだ。なお、この独自管理機能は、デリゲートにはないイベントだけのものである。イベントの使用をやめて、これを使ったコードをデリゲートに変更することは容易ではない。

17-7 継承とイベントの比較

 ある種のクラス・ライブラリを使っているC++などのプログラマーなら、なぜ独自のイベントという機能を使うのか分からないかもしれない。イベントを仮想メソッドと継承で実現しているクラス・ライブラリがあるからだ。それでは、継承でイベント機能を実装することと、C#のイベント機能の違いを見てみよう。以下に、List 17-8List 17-9と2つのソース・コードを示す。前者は継承を使ったもの。後者は、それとほぼ同じ内容をイベントで記述したものである。

  1: using System;
  2:
  3: namespace systemLibrary
  4: {
  5:   abstract class ClassBase
  6:   {
  7:     public abstract void onEvenet();
  8:   }
  9:   class ClassLibrary
 10:   {
 11:     public static void fireEvent( ClassBase instance )
 12:     {
 13:       instance.onEvenet();
 14:     }
 15:   }
 16: }
 17:
 18: namespace userProgram
 19: {
 20:   class ClassDerived : systemLibrary.ClassBase
 21:   {
 22:     public override void onEvenet()
 23:     {
 24:       Console.WriteLine("onEvent called");
 25:     }
 26:   }
 27:   class Class1
 28:   {
 29:     [STAThread]
 30:     static void Main(string[] args)
 31:     {
 32:       ClassDerived instance = new ClassDerived();
 33:       systemLibrary.ClassLibrary.fireEvent( instance );
 34:     }
 35:   }
 36: }

List 17-8

 List 17-8を実行するとFig.17-6のようになる。

Fig.17-6

 このプログラムは、2つの名前空間を持つ。3〜16行目は、システムが用意するライブラリという想定である。そして、18〜36行目が通常のプログラマーが記述するユーザー側の範囲と考えている。このプログラムのポイントは、イベントを通知する機能はシステム側にあるが、実際に呼び出されてほしいメソッドはユーザー側にあるという矛盾をどう解消するかである。これを実現するために、まず、ClassBaseというクラスが存在する。このクラスはイベントを伝達するonEventというメソッドを持つ。7行目で定義されているとおりである。しかし、ここにはabstractキーワードが付いており、中身が存在しない。中身はシステム側ではなく、ユーザー側で用意するものだからだ。実際の中身は、ClassBaseを継承したClassDerivedの中にある。22〜25行目がそれだ。22行目のoverrideキーワードにより、onEventメソッドに内容を与えている。その結果、システム側の名前空間にはClassDerivedクラスに関する情報が何もないにもかかわらず、システム側の名前空間からClassDerivedクラス内のメソッドを呼び出すことを可能としている。

 では、イベントを使ったList 17-9では、同じ処理がどう記述できるだろうか?

  1: using System;
  2:
  3: namespace systemLibrary
  4: {
  5:   public delegate void SampleEventHandler(object sender, EventArgs e);
  6:   class ClassLibrary
  7:   {
  8:     public static event SampleEventHandler sampleEvent;
  9:     public static void fireEvent()
 10:     {
 11:       sampleEvent( null, EventArgs.Empty );
 12:     }
 13:   }
 14: }
 15:
 16: namespace userProgram
 17: {
 18:   using systemLibrary;
 19:   class ClassDerived
 20:   {
 21:     public static void onEvent( object o, EventArgs e )
 22:     {
 23:       Console.WriteLine("onEvent called");
 24:     }
 25:   }
 26:   class Class1
 27:   {
 28:     [STAThread]
 29:     static void Main(string[] args)
 30:     {
 31:       systemLibrary.ClassLibrary.sampleEvent += new SampleEventHandler(ClassDerived.onEvent);
 32:       systemLibrary.ClassLibrary.fireEvent();
 33:     }
 34:   }
 35: }

List 17-9

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

Fig.17-7

 継承を使ったList 17-8と比べList 17-9は、行数がほぼ同じなので、どちらで書いても大差ないように見えるかもしれない。しかし、機能面では完全に同一というわけではない。まず、ClassBaseクラスが完全に姿を消して、5行目のデリゲートに姿を変えたことが分かるだろう。そして、8行目のイベントは、継承のサンプルには対応する記述がなかったものだ。ここで大きなインパクトを与えているのは8行目に記述されたイベントだ。イベントを用いることによって、以下の点が機能的に異なってくる。

  • 複数のハンドラを登録できる
  • 継承関係と無関係に呼び出せる
  • ハンドラのメソッド名を自由に変えられる

 特に2番目の特徴は重要だ。例えば、この後に出てくるフォーム上にボタンを貼り付けたサンプルでは、ボタンが押されたときに実行されるハンドラをフォームのクラス内に記述しているが、フォームとボタンは、共通の祖先を持つとはいえ、直接的な継承関係はない。ボタン内のメソッド呼び出しが、フォームのメソッド呼び出しとして処理されることはない。このように継承関係を完全に逸脱した位置にハンドラを置けることは、実際に見通しのよい分かりやすいプログラムを書くためには、大変重要な機能といえる。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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