著作一覧 |
ちょっと.NET WindowFormsアプリケーションを作っている。
そこで、イベント処理を書くわけだが、当然のように次のようにやる。
ctrl.AddEventHandler(delegate (object o, EventArgs e) { ... });
それから、今はdelegateなんて余分なキーワードはいらないんだなと思い出して
ctrl.AddEventHandler((o, e) => ....);
と、書く。
が、これはまずかった。
実際にはイベントによって次のハンドラに替えていくプログラムである。
void InitState() { ctrl.AddEventHandler((o, e) => FooState()); } void FooState() { ctrl.AddEventHandler((o, e) => BarState()); } ...
動かせば動かすほど、どんどん遅くなる。はて、なぜだ?
しばらくソースを見て、ふと気づく。ハンドラはAddしているが、Removeしていないじゃん。同じイベントハンドラが何度も呼ばれているからだ。最後に呼ばれるのが最後にAddしたやつだから見かけはうまく動いているだけだ。
そこでラムダ(匿名デリゲートでも同じだが)の恐怖を知る。
まっとうにRemoveを考えてみる。
void InitState() { EventHandler hnd = (o, e) => { ctrl.RemoveEventHandler(hnd); FooState(); }; ctrl.AddEventHandler(hnd); }
コンパイルできない。あれ、クロージャなんじゃなかったっけ? と一瞬とまどうが、定義中では変数hndの内容は未確定だ。……なんかだまされたような気がするが(時間軸上、必ず後になるはずだが、動的言語じゃないからそうはいかないのだろう)、しょうがない。(追記あり)
では、どう書けば良いのか?
void FooState() { ctrl.RemoveEventHandler((o, e) => FooState()); ctrl.AddEventHandler((o, e) => BarState()); }
コンパイルエラーにはならない……が、別インスタンスだよなぁ、そりゃ。
そのうち最適化がガリガリになされて、レキシカルスコープを持たない(という言い方であってるのかな? 独自の変数用のフレームを持たない)ラムダ式は同一インスタンスが割り当てられるようになるかも知れないけど、とにかくだめだし、そもそも、こんな冗長な書き方はごめんだ。
ということは、
void FooState(object o, EventArgs e) { ctrl.RemoveEventHandler(new EventHandler(FooState)); ctrl.AddEventHandler(new EventHandler(BarState)); }
うまく動く(newして別インスタンスを作ってはいるが、Equalsは中のポインタを見て等価性を判定しているのだろう)。
というわけで、結局、昔ながらの書き方に落ち着いてしまうのであった。
残念だよ。
追記:
つい、Javaのインナークラスの癖で考えてた。次のように書けばOKだ。
void InitState() { EventHandler hnd = null; hnd = (o, e) => { ctrl.RemoveEventHandler(hnd); FooState(); }; ctrl.AddEventHandler(hnd); }
が、これまた冗長だなぁ。設定と実行すべき処理の位置が一致しているのは良いけど、残念感はぬぐえない。
ジェズイットを見習え |
EventHandler hnd = null; と書くと通ります。このケースじゃなくても、再帰させたいときとかに使うテクニック(バッドノウハウ?)です。
あ、どうもありがとうございます。実は、落ち着いて考えたら同じ結論で、追記してから気づきました。
ctrl.AddEventHandler(hnd = (o, e) => {}); みたいに書けなくもないですが、確かにラムダ式をやめたほうが単純ですね。<br>ちなみにctrl.AddEventHandler(BarState); と書けます。
おお、その書き方は知りませんでした。どうもです。