コントロールのイベントを定義する際、
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アクセサを使って書くようにしています。というかしました。
最近読んだ本(プログラミング .NET Framework)によると、EventHandlerList を使用するのはパフォーマンス上の理由からだそうです。
例えば、System.Windows.Forms.Control には多数のイベントがありますが、大半は登録されません。Form 内には沢山の Control から派生したオブジェクトが存在するので、ほとんど使われないイベントのためにあるデリゲートフィールドが無駄にメモリを消費します。
EventHandlerList を使う方法だと、イベントの種類ごとに Key となる static な Object が必要なだけなので、メモリ消費量が少ない、という事「らしい」です^^;
当ブログ初のコメント、ありがとうございます&おめでとうございます。めでたくはないかも知れませんが。
パフォーマンス上の理由ったってなんかどうでもいいところって感じです。所詮null参照が使うメモリなんか8バイトだか16バイトだか、クラスが持つフィールド型情報分とかを考えても、イベントの数十でどうこうってメモリサイズでも無かろうと思うんですがね。
とか考えるのは富豪的プログラミングに慣れた者の暴言なんでしょうか。しかし.NET Frameworkそのものが富豪的プログラミングの方向性なんだし……。
しかもその構成をVB.NETでは不可能にしていた辺りとか。大規模開発にはC#使えと言うことなんでしょうかね。
ありがとうございます。^^
確かにあまりパフォーマンスとか考えて、トリッキーな事はしたくないですね。
ただ、この場合は、Control クラスにはイベントが 60 ほどあり、仮にフォームにボタンを 10 個貼るだけでも、660個!(フォーム自身も含めて)のあまり使われもしないデリゲートフィールドがあるのが我慢できなかったんでしょうね。それがどれだけ貼っても 60 で済むというのは、まぁ納得できます。
DataGrid とか高機能コントロールならばもっとでしょうか…。
>しかもその構成をVB.NETでは不可能にしていた辺りとか。大規模開発にはC#使えと言うことなんでしょうかね。
VB.NET をあまり使わないので知りませんでした。「ん〜」と唸ってしまいますね。
その10倍の6600個のフィールドでも「なんだ100kか」で済ましてしまう私がいます(苦笑)。
制約はメモリよりもハンドルとかGDIオブジェクトとかの方に圧迫感を感じてしまって。
VB2005で追加される構文は、どうしても冗長に感じてイヤですね。
VS使えばさっと出るんでしょうから打ち込みの手間はないんでしょうけど。
//ByValは省略したい手打ちユーザ