連載

C#入門

第13 言語に内蔵されたイベント機能

(株)ピーデー
川俣 晶
2001/10/11


独自のハンドラ管理

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

 1: using System;
 2: using System.ComponentModel;
 3:
 4: namespace ConsoleApplication57
 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:     static void Main(string[] args)
25:     {
26:       Console.WriteLine("add event handler");
27:       sampleEvent += new SampleEventHandler(handler);
28:       Console.WriteLine("remove event handler");
29:       sampleEvent -= new SampleEventHandler(handler);
30:       Console.WriteLine("done");
31:     }
32:   }
33: }
独自のハンドラ管理を記述したサンプル・プログラム7
addとremoveの2つのキーワードを使用して、独自のハンドラ管理を行うことができる。

これを実行すると以下のようになる。

サンプル・プログラム7の実行結果
ハンドルの登録/削除時にadd/removeのコードが実行されているのが分かる。

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

継承とイベントの比較

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

 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 ClassDelived : systemLibrary.ClassBase
21:   {
22:     public override void onEvenet()
23:     {
24:       Console.WriteLine("onEvent called");
25:     }
26:   }
27:   class Class1
28:   {
29:     static void Main(string[] args)
30:     {
31:       ClassDelived instance = new ClassDelived();
32:       systemLibrary.ClassLibrary.fireEvent( instance );
33:     }
34:   }
35: }
継承によりイベント機能を実装したサンプル・プログラム8
システム側(systemLibraryネームスペース)で抽象メソッドが定義されており、その実体はユーザー側(userProgramネームスペース)でオーバーライドする。

 これを実行すると以下のようになる。

サンプル・プログラム8の実行結果
システム側からユーザー側のメソッドが呼び出されている。

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

 では、イベントを使うと、同じ処理がどう記述できるだろうか?

 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 ClassDelived
20:   {
21:     public static void onEvent( object o, EventArgs e )
22:     {
23:       Console.WriteLine("onEvent called");
24:     }
25:   }
26:   class Class1
27:   {
28:     static void Main(string[] args)
29:     {
30:       systemLibrary.ClassLibrary.sampleEvent += new SampleEventHandler(ClassDelived.onEvent);
31:       systemLibrary.ClassLibrary.fireEvent();
32:     }
33:   }
34: }
上と同じ内容をイベントにより実装したサンプル・プログラム9
ユーザー側のネームスペース内には、システム側のClassBaseクラスについての記述はない。

 これを実行すると以下のようになる。

サンプル・プログラム9の実行結果
ユーザー側で定義されたハンドラがイベントの発生時に呼び出される。

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

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

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


 INDEX
  第13回 言語に内蔵されたイベント機能
    1.C#のイベント機能とは何か?
    2.イベントに引数を渡す
  3.独自のハンドラ管理
    4.ウィンドウにおけるイベントの使われ方
    5.複数ボタンに対応するハンドラ
 
「C#入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間