トップ回答者
WithEventsってパフォーマンス的に問題無いのですか?

質問
-
お世話になります、いつもこちらのフォーラムにはお世話になっております(ROMってばっかりですが・・・)
現在以下の環境にてシステム作成をしております
OS WindowsXP
VisualStudio2008 .NET 2.0 VisualBasic
今まで曖昧だった所を整理したくご教授下さい。
ButtonやTextBox等、様々なコントロールのイベントに関してですが
WithEventsで処理させる場合と(ドラッグ&ドロップではこちらが自動生成されますよね)
各イベント毎にAddHandlerをする場合する場合では内部的な
処理としてかなり違うのでしょうか?
WithEvent宣言されたコントロールは Handlesでプロシージャと
紐付け出来るので、とても便利なのですが、その反面
パフォーマンスが犠牲になっているのではないかと思っております。
もしかして別の「利点」があるのでしょうか?
利点があったとしても、パフォーマンス的に問題があるならば、
画面に多数のコントロールを設置している場合はWithEventを使用しない
ことも検討しなければいけないのではないか?
と思っています。
たとえば以下のコントロールは Me._Button1 でもアクセス出来ますよね
Friend WithEvents Button1 As System.Windows.Forms.Button
これって既に「余計な」処理が加わっているように思うのですが・・・
どうぞよろしくお願いします。
回答
-
but-masaru さんからの引用
ボタンの上などにマウスを重ねるとハイライトされたりしますが、
これは、Mouse_Enter等のイベントが発生しているのでは無いのでしょうか?開発者が新たにコードを書こうと、書くまいと、そのコントロールの中身で実装されているコードは実行されます。
but-masaru さんからの引用 つまり、WithEventsやAddHandler等を用いてButton.ClickやButton.Mouse_Enter等に対してプロシージャを書かないとしても
空の処理でイベントは常に発生しているととらえるべきなのでしょうか?前述のようにコントロールが何か処理をしています。
(その量はコントロールや送られてくるメッセージによって異なる)
イベントを呼び出すタイミングで、1個も登録されていなければイベントを呼ぶ処理がスキップされるだけです。
簡単なイメージ:
マウスがコントロールの上を通過→Windowsからメッセージが送られてくる→コントロールがそのメッセージをハンドル→何らかの処理を行う→イベントが登録されていればイベントを呼び出す
-
but-masaruさんおはようございます。
暗黙的に空のイベントハンドラが呼ばれるとイメージされているのであればそうではなく、暗黙的にAddHandler,RemoveHandlerが呼ばれるのです。
れいさんが書いておれる「SetではClass内のHandleキーワードに対応するため、Setされるオブジェクト、元のオブジェクトのイベントに対してAddHandler、RemoveHandlerを呼び出します。」をVBに直して書けばこんな感じです。
Code SnippetDim WithEvents Timer1 As Timer
Dim _Timer2 As Timer
Public Property Timer2() As Timer
Get
Return _Timer2
End Get
Set(ByVal value As Timer)
If _Timer2 IsNot Nothing Then RemoveHandler Timer2.Tick, AddressOf Timer1_Tick
_Timer2 = value
If _Timer2 IsNot Nothing Then AddHandler Timer2.Tick, AddressOf Timer1_Tick
End Set
End PropertyPrivate Sub Test()
For i As Integer = 0 To 100000
Timer1 = New Timer()
Next
For i As Integer = 0 To 100000
Timer2 = New Timer()
Next
End Sub
すべての返信
-
外池と申します。確証をもって詳しくご説明することはできないので、ご満足いただけないかもしれませんが・・・。(他の方のツッコミ大歓迎)
私の理解では、まったく差はないものと考えています。
WithEventsを用いてコントロールを宣言しHandlesを使ってイベントを記述する場合も、コンパイラーが生成するコードの中では、AddHandlerと同等の処理に展開されていると思います。AddHandlerというイベントを処理するメソッドを動的に割り当てる文と、Handlesという静的な割り当ての方法の両方を備えていることは、VBの言語としての特徴であって、.Net Frameworkとしての機能は、前者の動的な方法のみだと思います・・・。
ちなみに、C#には、AddHandlerに相当する動的な方法しかありません。
-----
以下は、禅問答のような感じなので、スルーしてもらって構いませんが・・・、参考までに。
もし、パフォーマンスに差があったとしても、ユーザーが実際にマウスやキーボードを操作して発生するイベントの数は、パフォーマンスの観点で問題になるような量ではないと思います。ですので、イベントに割り当てられたメソッドを呼び出すところでのオーバーヘッドは無視していいんじゃないかと。
それより、イベントに割り当てられたメソッドが重い処理だった場合には、大問題になり得ますが。
まぁ・・・、イベントというものは、「(コンピューターの速さから比べて)時々発生する事象をとらえて、サクっと処理する」という感じのものだと思います。
その点、人の操作で発生する事象は・・・、「時々」と考えて良いかと。
-
WithEventsで定義されたフィールドは、Handleキーワードでのイベント処理ができるよう、
VBによって少し変形されてコンパイルされます
まず、WithEventsフィールドはVBによってプロパティに自動で変換されます。
たとえば、
WithEvents TextBox1 as TextBox
の場合は、
TextBox1というプロパティと、_TextBox1というフィールドが自動で作成されます。
自動で定義されたGetはフィールドを返すだけですが、
SetではClass内のHandleキーワードに対応するため、
Setされるオブジェクト、元のオブジェクトのイベントに対してAddHandler、RemoveHandlerを呼び出します。
違いは、これだけです。
これによって、Handleキーワードが正確に動作します。
このため、いくつか注意が必要です。
------------------
・マルチスレッド
生成されるSet句はスレッドセーフには実装されていません。
マルチスレッドでHandle句を使ったりするときには気をつけないといけません。
・パフォーマンス
イベントの呼び出しはC#などと同じコストですが、
AddHandler/RemoveHandlerの分、実際にはプロパティに変換されるフィールドに対するSetのコストが高くなります。
WithEvents Timer1 As Timer
Dim Timer2 As Timer
Private Sub Timer1_Tick(xxx) Handles Timer1.Tick
Private Sub Test
for i=0 to 100000
Timer1 = New Timer()
next
for i=0 to 100000
Timer2 = New Timer()
next
End Sub
みたいなコードで確認すると差は歴然としています。
・リフレクション
WithEventsフィールドはILになるとプロパティになっていますので、
リフレクションを使うときには型が変わります。
------------------
この程度であろうと思います。
WithEventsを使うときには、イベント発生のコストではなく、
オブジェクトの割り当てのコストを考えねばいけないのに留意してください。
GUIでドラッグドロップしている場合はオブジェクトを作り直したりしませんから、
パフォーマンスはほとんど変わりません。
-
外池様、じゃんぬねっと様、れい様、なちゃ様
質問にお答え頂き、ありがとう御座います。
なるほど、皆様のご意見を総合的にとらえると
WithEventsにてイベントが指定されるのはそれほど
気にすることでは無いと言うことですね(VBを使っている以上)
私が思っていたのはWithEventsを使用すると暗黙的に
内部で空っぽのイベントが発生してしまっているのでは
無いか、、、という点でした。
AddHandlerを指定した場合や、C#での += new ..
などでは明示しているので、それ以外のイベントは発生
しないのでは無いのか、、、とも思っていました。
質問していて余計分からない部分が増えてしまったのですが
たとえば作成しているプロジェクトのプロパティにある
アプリーケーションタブにて「XP Visualスタイルを有効にする」
のチェックを入れると視覚効果が有効になりますよね?
ボタンの上などにマウスを重ねるとハイライトされたりしますが、
これは、Mouse_Enter等のイベントが発生しているのでは無いので
しょうか?
私はこれらのイベントはコントロールにWithEventsが設定されて
いるから自動的に起きていると思っていたのですが、再確認をし
てみたところ、設定されていなくてもその現象が起きますね。
つまり、WithEventsやAddHandler等を用いてButton.Clickや
Button.Mouse_Enter等に対してプロシージャを書かないとしても
空の処理でイベントは常に発生しているととらえるべきなので
しょうか?
コントロールが沢山貼り付けてあるWindowsForm上で
マウスポインタをぐりぐり(表現が悪くてすみません)
回していると、CPUの使用率がそのアプリケーションに
対してあがりますよね、これはつまり上記の現象が起きている
と理解するべきなのですか?
以下の内容、確認してみました---------------------------------------
IL DASMを使って実行ファイルをみたところ、WithEvent
キーワードを宣言したコントロールは以下の内容が追加
されている事を確認出来ました。
Prop コントロール名: instance class......
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
確かにこの中にはSetとGetが記述されていました。
VBでのAddHandler,C#での += new System.EventHandlerでは
上記のProp...が生成されないのも確認出来ました。
ILで見たときはC#の方がすっきりしていて気持ちいいですね、でも
パフォーマンスに差は無いと言うことですか・・・うーむ
WithEventsの 無し/有り に関しましては
Forループで10万回づつ回したところアドバイス通りの結果になりました
※Timerオブジェクトにて
Core2Duo L7300のPC 約 900ミリ秒 / 1200ミリ秒
Celeron 2.4GHzのPC 約 1700ミリ秒 / 2450ミリ秒
このことから、多少なりともWithEventsを指定することによって
オーバーヘッドが生じていると理解しました。
---------------------------------------------------------------- -
WithEventで生成したset_TextBox1とやらを見ると、メソッドの属性として"synchronized"がついてますね。
これと同等の意味合いなら一応、lockで保護しているという風に見て良いのかな…。
http://msdn2.microsoft.com/ja-jp/library/system.reflection.methodimplattributes.aspx
ILを深く理解する気はないのですが、軽く流し読みする限り、AddHandlerでは毎回、対象のインスタンスを取得するためにget_TextBox1をやるので若干気にはなります。
その点、set_TextBox1方式のHandlesではインスタンスはメンバー変数から取っているようなのでマシか?
といっても、大差が出るほどではないように見えますが…。
何度もやるわけじゃないですし。
-
but-masaru さんからの引用
ボタンの上などにマウスを重ねるとハイライトされたりしますが、
これは、Mouse_Enter等のイベントが発生しているのでは無いのでしょうか?開発者が新たにコードを書こうと、書くまいと、そのコントロールの中身で実装されているコードは実行されます。
but-masaru さんからの引用 つまり、WithEventsやAddHandler等を用いてButton.ClickやButton.Mouse_Enter等に対してプロシージャを書かないとしても
空の処理でイベントは常に発生しているととらえるべきなのでしょうか?前述のようにコントロールが何か処理をしています。
(その量はコントロールや送られてくるメッセージによって異なる)
イベントを呼び出すタイミングで、1個も登録されていなければイベントを呼ぶ処理がスキップされるだけです。
簡単なイメージ:
マウスがコントロールの上を通過→Windowsからメッセージが送られてくる→コントロールがそのメッセージをハンドル→何らかの処理を行う→イベントが登録されていればイベントを呼び出す
-
but-masaruさんおはようございます。
暗黙的に空のイベントハンドラが呼ばれるとイメージされているのであればそうではなく、暗黙的にAddHandler,RemoveHandlerが呼ばれるのです。
れいさんが書いておれる「SetではClass内のHandleキーワードに対応するため、Setされるオブジェクト、元のオブジェクトのイベントに対してAddHandler、RemoveHandlerを呼び出します。」をVBに直して書けばこんな感じです。
Code SnippetDim WithEvents Timer1 As Timer
Dim _Timer2 As Timer
Public Property Timer2() As Timer
Get
Return _Timer2
End Get
Set(ByVal value As Timer)
If _Timer2 IsNot Nothing Then RemoveHandler Timer2.Tick, AddressOf Timer1_Tick
_Timer2 = value
If _Timer2 IsNot Nothing Then AddHandler Timer2.Tick, AddressOf Timer1_Tick
End Set
End PropertyPrivate Sub Test()
For i As Integer = 0 To 100000
Timer1 = New Timer()
Next
For i As Integer = 0 To 100000
Timer2 = New Timer()
Next
End Sub -
Azulean さんからの引用 ILを深く理解する気はないのですが、軽く流し読みする限り、AddHandlerでは毎回、対象のインスタンスを取得するためにget_TextBox1をやるので若干気にはなります。
その点、set_TextBox1方式のHandlesではインスタンスはメンバー変数から取っているようなのでマシか?
この件、私のつたない読解力でILを見てみたのですが、WithEventsを使わないコントロールに
AddHandlerにてイベントを実装した場合は get_...というメソッドは生成されてませんでした
WithEvents にてイベントを実装した場合は以下の内容が生成されていました
Method get_...
Method set_...
Prop .......
見るところ間違えているかな・・?
メソッドの属性って、ILで確認出来るんですね!
れい様がおっしゃっていた 「Set句がスレッドセーフでは無い」というのはILをみれば分かることだったんですね
instance void set_[コントロール](ほにゃらら) cil managed synchronized
Azulean様のアドバイスでどの様に見るのかやっと分かりました!(み、見方が分かっただけですけど・・)
うーむ、いろいろ理解するには時間が必要ですねぇ
また一つ勉強になりました、ありがとう御座います -
三輪の牛 さんからの引用 but-masaruさんおはようございます。
暗黙的に空のイベントハンドラが呼ばれるとイメージされているのであればそうではなく、暗黙的にAddHandler,RemoveHandlerが呼ばれるのです。
具体的な提示をして下さり、ありがとう御座います。
件名になっていた「パフォーマンス」に関しては、イベントの挙動ではなく
WithEventsによって暗黙に置き換わったget,set(特にset)の挙動、
つまりProperty Methodのパフォーマンスがどうなのか?
という話になるわけですよね
このあたりがちゃんと分かっただけでもスッキリしました。
(皆さんはじめからそー言っていますよね;;)
C#には存在しないというのはもともとVB独自のキーワードだったからなのですね。
こういう部分も知りませんでした(大枠は両方同じだと思ってましたので)
get,setが生成されるのが「いやだー」な場合はWIthEventsを削除すれば良いわけで、、、
(でもWithEventsがとられたコントロールをダブルクリックすると・・・やっぱり Handles生成!
開発環境はWithEventsを使う前提で動くようになっているということも分かりました。)
WithEventsを宣言することによってHandlesが利用できる点に関しては、
確かに視覚的にわかりやすいですね!
この点は重宝しています(私がC#乗り換えに踏み込めない1つ目の理由)
ありがとう御座いました、この件は解決にしたいと思います。
これからも勉強させて頂きます(^^