2005年09月19日

Componentとイベント

コントロールのイベントを定義する際、

public event EventHandler Something;
protected virtual void OnSomething(EventArgs e) {
    if (Something != null)
        Something(this, e);
}
と書いてました。ごく自然ですね。

しかし最近リフレクションを使ってコントロールのイベントを漁ってた所、どうもこういう単純な構成ではなく、Component.Eventsプロパティを使っているらしいことが分かってきました。そういえばあったねこんなの、程度の認識でしたが、改めてイベントの使用方法のガイドラインに目を通してみると、これを使った例が挙げられていました。イベントが多すぎて一々フィールド(イベントの実体はデリゲートインスタンスフィールドです)を用意するのが大変な場合に使用するイベントプロパティという構成だそうで。

それに従った場合、上のコードはこう書き直されます。

private static readonly object EventSomething = new object();
public event EventHandler Something {
    add { base.Events.AddHandler(EventSomething, value); }
    remove {base.Events.RemoveHandler(EventSomething, value); }
}
protected vitual void OnSomething(EventArgs e) {
    EventHandler handler = base.Events[EventSomething] as EventHandler;
    if (handler != null)
        handler(this, e);
}

率直に言って今一つ利点が見えないのですが。まあイベント一覧を取得するのには便利なのかな……? こうやった場合、そのクラス内でSomethingをデリゲートのように扱うこともできなくなるので、必ずOnSomethingを通して呼び出す必要がありますし。

そこで問題になるのがVB.NET。VB.NETではadd/removeアクセサを書くことができません。VB2005で追加されましたが。まあVB.NETではこれまで通りで、VB2005用にはC#と同等の記述をすると言うことで。ちなみにVB2005で上のコードを書くとこうなります。AddHandler/RemoveHandlerアクセサの他にRaiseEventアクセサを記述する必要があるのが特徴ですね。代わりにイベントを起こす側はRaiseEventを書くだけで済むので楽と言えば楽です。

Private Shared ReadOnly EventSomething As Object = New Object()
Public Custom Event Something As EventHandler
    AddHandler(ByVal Value As EventHandler)
        MyBase.Events.AddHandler(EventSomething, Value)
    End AddHandler
    RemoveHandler(ByVal Value As EventHandler)
        MyBase.Events.RemoveHandler(EventSomething, Value)
    End RemoveHandler
    RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
        Dim handler As EventHandler = _
            TryCast(MyBase.Events(EventSomething), EventHandler)
        If Not(handler Is Nothing) Then handler(Me, e)
    End RaiseEvent
End Event
Protected Overridable Sub OnSomething(ByVal e As EventArgs)
    RaiseEvent Something(Me, e)
End Sub

さてここで、C#とVB2005のコードで決定的な差が出ています。すなわち、実行するハンドラを呼出側が決定するかイベント側が決定するかです。

C#では呼出側、つまりOnSomethingがどのイベントハンドラを呼び出すかを決定します。OnSomethingはvirtualですから、これがオーバーライドされてかつbase.OnSomethingが呼ばれないと、他のメソッドからOnSomethingが呼ばれてもSomethingイベントハンドラは永遠に呼ばれないと言うことになります。

VB2005では、イベント側がイベントハンドラを規定します。呼び出す側はRaiseEventを使ってイベントを発生させます。そうすると、他のメソッドからはOnSomethingを通さず直接RaiseEventを使用するでしょう。ですからOnSomethingをオーバーライドされても他のメソッドには何の影響も出ないことになります。

どちらが優れているかは一概には言えません。また、C#にせよVB2005にせよ、お互いの基本コードを実装できます。

C#なら、privateなraise_Somethingメソッドを書き、ここに(上で書いた)OnSomethingの実装を移し、OnSomethingからはraise_Somethingメソッドを呼ぶだけにして、他のメソッドもOnSomethingではなくraise_Somethingを呼ぶようにすればOnSomethingのオーバーライドは何の影響もなくなります。

逆にVB2005でも、OnSomethingメソッドにRaiseEventステートメントではなくCustom Event内で書いたRaiseEventブロック部分を実装させ、他メソッドからもRaiseEventではなくOnSomethingを呼ぶようにすれば、OnSomethingのオーバーライドによってSomethingイベントの発生をさせなくさせることが可能になります。

ちなみに私はComponent派生クラスのイベントはadd/removeアクセサを使って書くようにしています。というかしました。

posted by Hongliang at 16:20| Comment(4) | TrackBack(1) | .NET | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
激しく遅いコメントですが…。

最近読んだ本(プログラミング .NET Framework)によると、EventHandlerList を使用するのはパフォーマンス上の理由からだそうです。
例えば、System.Windows.Forms.Control には多数のイベントがありますが、大半は登録されません。Form 内には沢山の Control から派生したオブジェクトが存在するので、ほとんど使われないイベントのためにあるデリゲートフィールドが無駄にメモリを消費します。
EventHandlerList を使う方法だと、イベントの種類ごとに Key となる static な Object が必要なだけなので、メモリ消費量が少ない、という事「らしい」です^^;
Posted by 囚人 at 2005年10月16日 16:24
>囚人さん
当ブログ初のコメント、ありがとうございます&おめでとうございます。めでたくはないかも知れませんが。
パフォーマンス上の理由ったってなんかどうでもいいところって感じです。所詮null参照が使うメモリなんか8バイトだか16バイトだか、クラスが持つフィールド型情報分とかを考えても、イベントの数十でどうこうってメモリサイズでも無かろうと思うんですがね。
とか考えるのは富豪的プログラミングに慣れた者の暴言なんでしょうか。しかし.NET Frameworkそのものが富豪的プログラミングの方向性なんだし……。
しかもその構成をVB.NETでは不可能にしていた辺りとか。大規模開発にはC#使えと言うことなんでしょうかね。
Posted by Hongliang at 2005年10月16日 18:06
>当ブログ初のコメント、ありがとうございます&おめでとうございます。
ありがとうございます。^^

確かにあまりパフォーマンスとか考えて、トリッキーな事はしたくないですね。

ただ、この場合は、Control クラスにはイベントが 60 ほどあり、仮にフォームにボタンを 10 個貼るだけでも、660個!(フォーム自身も含めて)のあまり使われもしないデリゲートフィールドがあるのが我慢できなかったんでしょうね。それがどれだけ貼っても 60 で済むというのは、まぁ納得できます。
DataGrid とか高機能コントロールならばもっとでしょうか…。

>しかもその構成をVB.NETでは不可能にしていた辺りとか。大規模開発にはC#使えと言うことなんでしょうかね。
VB.NET をあまり使わないので知りませんでした。「ん〜」と唸ってしまいますね。
Posted by 囚人 at 2005年10月17日 22:23
>ただ、この場合は、Control クラスにはイベントが 60 ほどあり、仮にフォームにボタンを 10 個貼るだけでも、660個!(フォーム自身も含めて)のあまり使われもしないデリゲートフィールドがあるのが我慢できなかったんでしょうね。それがどれだけ貼っても 60 で済むというのは、まぁ納得できます。

その10倍の6600個のフィールドでも「なんだ100kか」で済ましてしまう私がいます(苦笑)。
制約はメモリよりもハンドルとかGDIオブジェクトとかの方に圧迫感を感じてしまって。

VB2005で追加される構文は、どうしても冗長に感じてイヤですね。
VS使えばさっと出るんでしょうから打ち込みの手間はないんでしょうけど。
//ByValは省略したい手打ちユーザ
Posted by Hongliang at 2005年10月18日 00:50
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

[programming]event, delegate
Excerpt: http://www.codeproject.com/cpp/acfdelegate.asp C#のようにoperator overloadでAddEventListener,RemoveEvent..
Weblog: beruponの日記
Tracked: 2007-02-12 07:58

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。