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

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

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

17-3 イベントのハンドラに引数を渡す

 イベントを呼び出すときには、ハンドラへ引数を渡すことができる。その方法を紹介したのがList 17-3のサンプル・ソースである。

  1: using System;
  2:
  3: namespace Sample003
  4: {
  5:   public class SampleEventArgs : EventArgs
  6:   {
  7:     public string message;
  8:   }
  9:   public delegate void SampleEventHandler(object sender, SampleEventArgs e);
 10:   class Class1
 11:   {
 12:     public event SampleEventHandler sampleEvent;
 13:     public void handler( object o, SampleEventArgs e )
 14:     {
 15:       Console.WriteLine(e.message);
 16:     }
 17:     [STAThread]
 18:     static void Main(string[] args)
 19:     {
 20:       Class1 target = new Class1();
 21:       target.sampleEvent += new SampleEventHandler(target.handler);
 22:       SampleEventArgs sampleEventArgs = new SampleEventArgs();
 23:       sampleEventArgs.message = "in main method";
 24:       target.sampleEvent( target, sampleEventArgs );
 25:     }
 26:   }
 27: }

List 17-3

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

Fig.17-3

 イベントは複雑に絡み合った仕組みであるため、好き勝手に自分でハンドラの引数を決定できない。そのため、System.EventArgsクラスを継承して、自分が渡したい情報を記録できるクラスを作り出して、これを使う。もともとイベントは、EventArgs型のインスタンスを引数として持つので、これを経由して引き渡す。このクラスを継承したクラスなら、安全にEventArgs型にキャストできるので、ハンドラに渡しても問題ない。

 そこで、実際に文字列を1つ持つクラスを書いてみたのが5〜8行目だ。そして、22〜24行目を見ていただきたい。22行目で引数クラスのインスタンスを作り、23行目でインスタンスの中に引数として渡したい文字列を格納している。24行目では、第2引数にこのインスタンスを指定している。これにより、イベントはハンドラを呼び出し、15行目が実行される。第2引数の中には文字列が入っているので、15行目のe.messageは文字列を指定する。その結果、15行目は引数をコンソールに表示させる。

17-4 staticなイベント

 イベントはメンバー変数のように記述し、ハンドラはメソッドそのものである。ということは、それらにstaticキーワードを付けることも実際に可能である。staticなイベントとハンドラ、つまり、クラスのインスタンスを生成することなく利用できるイベントとハンドラを記述した例をList 17-4に示す。

  1: using System;
  2:
  3: namespace Sample004
  4: {
  5:   public delegate void SampleEventHandler(object sender, EventArgs e);
  6:   class Class1
  7:   {
  8:     public static event SampleEventHandler sampleEvent;
  9:     public static void handler( object o, EventArgs e )
 10:     {
 11:       Console.WriteLine("handler called");
 12:     }
 13:     [STAThread]
 14:     static void Main(string[] args)
 15:     {
 16:       sampleEvent += new SampleEventHandler(handler);
 17:       sampleEvent( null, EventArgs.Empty );
 18:     }
 19:   }
 20: }

List 17-4

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

Fig.17-4

 8行目にstaticキーワードが増えていることが、イベントがstaticであることを示している。また9行目にstaticキーワードがあることが、ハンドラがstaticであることを示している。このサンプル・ソースは、List 17-1をstaticを使う形に修正しただけのものなので、あまり多くの説明は必要ないだろう。

17-5 イベントではできないこと

 イベントは、あるクラスで起こった出来事の通知を目的とした機能である。つまり、イベントを通知する側は、通知すべき出来事が起こったクラスそのものである。受け取る方はだれでもよいが、当事者でもないクラスがイベントを発生させることは適切ではない。そのため、どんなインスタンスのどんなメソッドでも、条件さえ合えばハンドラとして登録できるのだが、イベント呼び出しは、イベントが記述されたクラス内からしか行うことができない。

 List 17-5のサンプル・ソースは、実際にコンパイルさせるとエラーになる。

  1: using System;
  2:
  3: namespace Sample005
  4: {
  5:   public delegate void SampleEventHandler(object sender, EventArgs e);
  6:   class Class1
  7:   {
  8:     public event SampleEventHandler sampleEvent;
  9:   }
 10:   class Class2
 11:   {
 12:     public void handler( object o, EventArgs e )
 13:     {
 14:       Console.WriteLine("handler called");
 15:     }
 16:     [STAThread]
 17:     static void Main(string[] args)
 18:     {
 19:       Class1 target1 = new Class1();
 20:       Class2 target2 = new Class2();
 21:       target1.sampleEvent += new SampleEventHandler(target2.handler);
 22:       target1.sampleEvent( target2, EventArgs.Empty );  // イベント 'Sample005.Class1.sampleEvent' は += 、-= の左辺にのみ使用できます。ただし、 'Sample005.Class1' 型内から使用されている場合を除きます。
 23:     }
 24:   }
 25: }

List 17-5

 21行目のハンドラの登録は問題なくできる。イベントを通知される側には制約がないからだ。しかし、22行目のイベント呼び出しのコードはエラーになる。Class2の中から別クラスであるClass1の中に記述されたイベントを呼び出すことは適切ではないのだ。22行目の内容は、Class1の内部にあれば利用可能になる。なお、イベントを使わず、デリゲート機能を直接使えば、このような制約はなく、どのクラスからでも自由に扱える。しかし、それはすでにクラス内で起こった出来事を伝えるという役割を果たさないので、イベントの目的を逸脱してしまっているといえる。

 イベント呼び出しは、イベントと同じクラス内からしかできない、という制約は継承にも適用される。List 17-6のサンプル・ソースを見ていただきたい。

  1: using System;
  2:
  3: namespace Sample006
  4: {
  5:   public delegate void SampleEventHandler(object sender, EventArgs e);
  6:   class Class1
  7:   {
  8:     public event SampleEventHandler sampleEvent;
  9:   }
 10:   class Class2 : Class1
 11:   {
 12:     public void handler( object o, EventArgs e )
 13:     {
 14:       Console.WriteLine("handler called");
 15:     }
 16:     [STAThread]
 17:     static void Main(string[] args)
 18:     {
 19:       Class2 target2 = new Class2();
 20:       target2.sampleEvent += new SampleEventHandler(target2.handler);
 21:       target2.sampleEvent( target2, EventArgs.Empty );  // イベント 'Sample006.Class1.sampleEvent' は += 、-= の左辺にのみ使用できます。ただし、 'Sample006.Class1' 型内から使用されている場合を除きます。
 22:     }
 23:   }
 24: }

List 17-6

 21行目は、一見同じクラス内の出来事を扱っているように見えるが、実体は継承を経由した別クラスにあるので、エラーになってしまう。同じクラス内という条件はまさに厳密に同じクラス内ということで、継承されたクラスは同じクラスとは見なされない。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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