one time event

前回、まだ続きがあるよと書きつつ今回は別なお話。


今回のタイトルは「one time event」
なんとなくかっこよさげな名前。
と言いつつ、これは僕が勝手につけた名前です。
別に大袈裟なことではないのですがなんとなく思いついたのでメモ代わりに書いておきます。


要は一度だけ実行されるイベントです。
コードにするとこんな感じ

void OneTimeButton_Click(object sender, EventArgs e)
{
    MessageBox.Show("One Time Event!!");
    ((Control)sender).Click -= OneTimeButton_Click;
}

このコードが何かのボタンのClickイベントにインストールされているとします。
一度目にクリックしたときはもちろんイベントは呼ばれますが、その中で自分自身をコントロールのClickイベントから削除しています。
なんという自殺願望。


ちなみにこのイベントがClickイベント以外にインストールされていた場合はアウトっ!です。


これだけ。
ものすごく単純なお話。
正直あまり使いどころがない感じ。
つうかこんなの当たり前。


これで終っちゃうと本当に使いどころがないコードの紹介で終ってしまうので、僕が実際のコードで使った例を紹介します。


Silverlightという、怒られそうな言い方で説明するとFlashのようなブラウザプラグインがあります。
それはC#でも開発できるので少しいじったことがあるのですが、その中で使ったコードです。
まず前提知識として、ItemFall変数はStoryboardクラスのインスタンスです。そしてImageBoard_Clickというのは自分で定義したイベントです。
今回それらの名前に大した意味はありません。まぁ、fooやbarよりはましということで。

void CellImage_Click(object sender, MouseButtonEventArgs e)
{
    EventHandler ItemFall_Completed = null;
    ItemFall_Completed = (S, E) =>
      {
        ItemFall.Stop();
        ItemFall.Copmleted -= ItemFall_Completed;
    
        ImageBoard_Click(sender, e);
      };

    ItemFall.Completed += ItemFall_Completed; 
    ItemFall.Begin();
}

かなりはしょったコードなので、僕が使ったものそのままというわけではないのですが大枠はそのままです。


まずSilverlightにはStoryboradという各種さまざまなアニメーションを行うためのクラス(なのかな?)があります。
それを使ってクリックイベント内でアニメーションをスタートさせ、アニメーションが終わったというイベント内でアニメーション起動のきっかけになったクリックイベントで本当は行いたかった処理をさせています。
今回クリックイベント内で行いたかった処理はImageBoard_Clickというイベントを実行して、それにsenderとeを渡すということです。
ここで注意が必要なのは、ImageBoard_Clickに渡したい引数のsenderとeはあくまでクリックイベントのもので、アニメーションが終わったというイベントのそれではありません。
つまりクリックイベントのsenderとeを保存しておいて、アニメーション終りのイベント内で使いたいということです。


一つのクラス内にまとまっているならインスタンス変数に退避させればいい。と、いう解決策もあります。正直それでもできます。むしろそっちのほうがわかりやすいかもしれません。
しかし僕は局所的な変数のためのインスタンス変数は極力作りたくないというスタンスなのでそれはしない方向で考えます。
Storyboardにobject型で任意のデータを格納できるTagのようなインスタンス変数があればそれを使うのですが、残念ながら僕がこのコードを書いた時点ではありませんでした。(今あるかどうかは知りません)


なので思いついたのがクロージャです。
クロージャについて簡単に説明するのは難しいのでWikipediaでも参照してください。
早い話が、関数が定義された時点の環境を保存しておいて、違うタイミングで実行された場合でもその変数を参照できるみたいなことです。(かなり乱暴なので鵜呑みにしないでね)


この機能を使えば、違うタイミングで呼び出されるイベント内でClickイベントのsenderとeが参照できます。
俺大満足。


しかし気をつけなければならないことが一つ。
このlambda式で生成されている関数は、Clickイベントごとに実体が異なるということです。
なので、この記事で紹介したone time eventのようにそのイベントの中で自分自身を毎回消去するということが必要になってきます。
これをしなければ、Clickイベントの度に新しいイベントが生成され、アニメーション終りイベントでマルチキャストされるイベントがどんどんと増えていくという事態になってしまいます。


そしてもう一つ。
例のように、lambda式や無名関数定義の中で自分自身の変数を参照したい場合は、一度その変数をnullなどで初期化する必要があります。
これをしなければコンパイラに怒られます。


ちなみに一度nullで初期化というのは、lambda式を再帰関数にしたい場合にも必要です。
nullで初期化しなければ関数本体で自分を参照できないので、考え方は同じです。


結局例もかなり特殊な場合なような気がするので、使いどころがあるのかどうか分からないものの紹介になってしまいました。
まぁ僕は心の赴くままにコーディングするのでその辺は気にしない気にしない。