機能の拡張 -イベント-

ホームC#プログラミング応用講義 > イベント

目次

ポピュラーなイベント

this.Load += new System.EventHandler(this.Form1_Load);
非常によく使う、入門講座で説明した「イベントの追加」というものは、上のような形で表現されます。

+= 演算子がやはり目に付きますね。前回デリゲートをやりましたから、怪しいと思うのは当然です。
何かデリゲートが絡んでそうな雰囲気です。

そこでちょっと試してみましょう。フォームを用意し、label1, label2 をそれぞれ用意します。
この辺は VisualStudio.NET を使うなり、自分で書くなり、入門講座から流用するなりしてください。

その中で、上に示したイベントの追加を記述し、その下にこれも追加してください。
this.Load += new System.EventHandler(this.Load2);
上に示したのとは微妙に違います。括弧の中が違いますね。

そして、以下のブロックも追加してください。
private void Form1_Load(object sender, System.EventArgs e)
{
    label1.Text = "Form1_Load からの表示\n";
}

private void Load2(object sender, System.EventArgs e)
{
    label2.Text = "Load2 からの表示\n";
}

これをコンパイルして実行すると、label1, label2 それぞれに文字列が表示されます。
通常使っている「Form1_Load」には label1 しかありませんから、きちんと 「Load2」 も呼び出されていることがわかります。

普通はこのような面倒なことはしません。Load イベントに追加するメソッドは1つが普通です。
が、いくつも追加できるということから考えると、やはりデリゲートが絡んでおり、チェーンのようにメソッドがつながっていると確信できるでしょう。

イベントの使い方

デリゲートは絡んでいても、デリゲートなんてどこにも宣言してないぞ。
たしかに、プログラマである皆さんはデリゲートを宣言することなく、デリゲートが絡んでいるイベントを使っています。

しかし、以下の部分を見てください。
public class Form1 : System.Windows.Forms.Form
これは継承、既習の範囲です。

派生クラスは基本クラスの内容をがっぽり飲み込む
と、繰り返して言っていましたが、まさにその通り。自分で宣言したクラス Form1 は、いつのまにか .NET Framework の作者が定義した Form クラスの内容をばっぽり吸収しています。

ここで示した Form や、Web アプリケーションの Page クラスは継承して利用するのが目的ですので、自分で作ったクラスにいつの間にか機能が追加されています。
今問題となっている「デリゲート」も、Form の中にある、かと考えられます。

しかし、Load イベントはこの Form クラスの中に定義されていますが、デリゲートは定義されていません。イベントとデリゲートは同じものではありません。
this.Load += new System.EventHandler(this.Form1_Load);
まだ何も言っていないので難しいとは思いますが、デリゲートの部分はどこでしょう? このステートメントの中にあります。

this.Load
は、this がありますから Form1 に定義されています。(Form の定義したものが飲み込まれて Form1 にもあるということ)

this.Form1_Load
も、下の方にあるはずです。自分で追加しましたね、「Load2」と一緒に。

new
これは問題外です。オブジェクト生成のキーワードですから。

となると、
System.EventHandler
これしか残りません。これがまさしくデリゲートなのです。

自分でイベントを追加する際は、
イベント名 += new デリゲート名(追加するメソッド名);
となります。イベントの定義方法などはまだわかりませんが、これでイベントを使う方法が確実に身に付いたでしょうか?

イベントを作る

始めに言います。自分でイベントを作ることはめったにない。

なぜなら、自分で作るよりスマートなものがあらかじめ .NET Framework に用意されているからです。
ほとんどすべての場合、この用意されたイベントに、上の方法でメソッドを追加すれば事足ります。

しかし、せっかくですから自分でも作れるようにしましょう。
どうせだからデリゲートを作り、イベントを作り、イベントに追加し、人為的にイベントを発生させてみましょう。なかなか興味深いですよ。

で、その手順はたった今述べました。「デリゲートを作り」、「イベントを作り」、「イベントに追加し」、「人為的にイベントを発生させ」る。
あとはこれにいくつかの決まりを習得すれば簡単にできます。

いくつかといいましたが修正します。1つです。イベントの大原則。
イベントは送信側受信側があり、ベント自体は送信側に定義し、送信側から呼び出す
そしてイベントに追加するメソッドは受信側に定義しておき、受信側から追加する。

これに関しては、サンプルを見た後で実感してください。

サンプルの前にイベント定義の構文を知らなければなりません。それは以下の通りです。
public event デリゲート名 イベント名;
public は、同一クラスから呼び出すのであれば当然 private でもかまいませんが、イベントは外から呼び出す場合が多いので public が多いです。

event はキーワード。つづりからすると「エヴェント」ですが、イベントと読みます。始めを i にしないように。
デリゲート名は、あらかじめ定義されたデリゲートの名前を、イベント名は作りたいイベントの名前を書きます。

イベントを実装した、余り意味のないサンプルですが、使い方を確認してください。
using System;

delegate void TRICK();

class Test
{
    public event TRICK TRICKevent;
    
    public void CallEvent()
    {
        if (TRICKevent != null)
            TRICKevent();
    }
}

class TestDemo
{
    public static void Main(string[] args)
    {
        Test t = new Test();
        
        t.TRICKevent += new TRICK(TRICK1);
        
        t.CallEvent();
    }
    private static void TRICK1()
    {
        Console.WriteLine("TRICK1 は深夜番組の出世頭です。");
    }
}

まずデリゲート TRICK が定義されています。そしてクラス Test の中に、イベントが定義されています。
ここでは「TRICK」というデリゲートを使い、「TRICKevent」というイベントを作ってるんだなということがわかります。

その下はちょっと読み飛ばして TestDemo クラスにジャンプ。
いつも通り Main() があって、普通に Test クラスからオブジェクトを生成し、参照を t に代入しています。

次のステートメントは既習です。このページの上の方で学んだイベントの追加、実際には「イベントに追加」するステートメントです。
左からイベント名、new、デリゲート名、追加するメソッドですから、追加するメソッドとは何だと探すと、1行(ステートメント)飛ばして下にあります。

単なる文字列を表示する機能が実装されたメソッド「TRICK1」。前回のデリゲートの流用です。
それはともかく、これが追加されたとわかれば結果はこの文字列が表示されると予想できますが、まだ先を見ましょう。先といっても戻りますが。

ちょっと上に上がって
t.CallEvent();
この部分です。t には Test がはいってるから、Test の中の CallEvent メソッドを呼び出しているな。ではそちらに目を移します。上の方ですね。

if による判別式がありますが、これは何のことか。
null に関してはこれまで詳しく説明しませんでしたが、要するに「空っぽ」「参照なし」の状態です。(ぬる と読む)

例えば、
string str;
Console.WriteLine(str);
str は、宣言されただけで代入されていません。よってこれは null 参照(参照なしを参照!?)と呼ばれるエラーです。ちなみに string 型は値型ではなく参照型ですので null もありうるのです。

で、
TRICKevent != null
というと、イベントの中身は空っぽじゃないよね、空っぽじゃなければそのままいくよ、ということをさしています。簡単にイベントが null 参照であるときを避けています。

ここでは
t.TRICKevent += new TRICK(TRICK1);
で、追加してますから空っぽのはずはありません。なのでそのままいくとイベントを呼び出すわけです。

イベントが呼び出されると、イベントに追加されたすべてのメソッドが呼び出されます。ここでは1つだけ、TRICK1 ですから
TRICK1 は深夜番組の出世頭です。
だけが表示されます。表示された文字列に納得してから、一件落着。

イベントはこのような順序で呼び出されます。で、大原則に戻ると「イベント送信側」というのはイベントを送信する側(←そのままじゃん)ですから Test になり、イベントを受け取る側が受信側ですから TestDemo になります。
送信側にイベントを定義し、直接イベントを呼び出し、受信側でイベントにメソッドを追加する。これが大原則です。

うそつけ、受信側の TestDemo から
t.CallEvent();
で呼び出しているじゃないか。「コールイベント」=「イベントを呼ぶ」 だろう、と思われるかもしれません。これは引っ掛けです。

CallEvent() はイベントではなく、ただのメソッドです。真のイベント名を確認してください。TRICKevent ですよね。
ですから、実際に呼び出しているのは CallEvent() の中ですから確かに送信側に定義されています。
CallEvent() は間接的に呼び出していますが、直接の呼び出しは TRICKevent() なわけです。

そしてまた、イベントはすべてマルチキャストですから、デリゲートの戻り値の型は void が一般です。
デリゲートのシグニチャ(名前を除く)と、TRICK1() のシグニチャは一致していますね。void と引数を取らない点が共通です。一致していなければ追加できません。

では、イベントに関しては以上です。イベントも追加することもできれば削除することもできますが、ほとんど使われません。-= で削除されるところを見るとなかなか面白いのですが、それは余裕があれば自分で調べるなりしてください。
とにかく、イベントをフルに利用するだけでなく、自分で定義できるのですからこれで十分。これで定義済みのイベントにメソッドを追加する際も迷わずできますしね。

[ ステップアップC# ] [ C#プログラミング応用講義 ]