C#のイベント機能を勉強してみる

はいどーもこんばんは熱にうなされてるツミオです。
そろそろC#の基本文法については抑えられてきたと思うので、本日はC#のイベント機能を勉強してみたいと思います(デリゲートの基礎は理解している前提で進めます)。
https://msdn.microsoft.com/ja-jp/library/5z57dxz2%28v=vs.110%29.aspx
上記のサイト(たぶん日本語表示になってると思いますが、もし英語表示だったら画面上部の翻訳ボタン押してください)をサンプルにして進めていきますので、開いておいてくださいね。

イベントとは

そもそもイベントとは何でしょうか。
『独学C#』によると「イベントは、本質的に、あるアクションが発生したことを自動的に通知するための仕組みです」とのことです。
例えばマウスがクリックされたとき、イベントを通知して何らかのアクションを起こします。
ツクラーの方向けに言うと、JavaScriptでもよく「イベントリスナを登録する」とか言いますよね。
C#でもそれと同じです(たぶん)。

イベント実装のための準備

MSDNのサンプルを開いてください。
とりあえずこの通りに実装していきましょう。

1.Countdownクラスを作成
2.EventHandler型のイベントCountdownCompletedを定義する

こんな感じですね。
イベントは本質的にデリゲートと同じらしいので、上記アドレスの説明では「イベント メンバーの型に System.EventHandler デリゲートを設定します」と書かれていますね。

イベントを発生させるためのメソッド

次はイベントを発生させるためのメソッドを実装します。
具体的には以下の通り。

1.protectedメソッドOnCountdownCompletedを宣言する
2.メソッド内でイベントを発生させる

イベントを発生させるためのメソッドは「ON+イベント名」で名付けるのが慣例となっているようです。
今回ならON+CountdownCompletedでOnCountdownCompletedですね。

メソッドの内部でCountdownCompleted?.Invoke(this, e);とありますが、これはいわゆる「nullチェック」をしています。
MSDNのサンプルコードでは

と書かれていますが、C#6.0以降の機能が使えるなら、null条件演算子を使ったCountdownCompleted?.Invoke(this, e);の方がスマートです。

ちなみにCountdownCompletedの引数と戻り値はEventHandler型に従います。
つまり引数は(object sender, EventArgs e)で、戻り値はなしです。
もしこのEventHandler型がAction型だったなら、引数も戻り値もないということですね。
イベントは一般的にこのEventHandler型のデリゲートを使うことが多いようです。

ON~メソッドを呼び出す

イベントを発生させるためのメソッドが完成したので、次はこのメソッドをどこかで呼び出さなければなりません。

1.カウンタ変数internalCounterを宣言
2.Decrementメソッドを実装
3.Decrementメソッド内でOnCountdownCompletedメソッドを実行し、イベントを発火させる

こんな感じで作っていきましょう。

Decrementメソッドが実行されるたび、internalCounter変数の値が1減ります。
このinternalCounter変数の値が0になったとき、OnCountdownCompletedメソッドが実行され、結果としてCountdownCompletedイベントが実行されます。
また、Decrementメソッドが実行されるたび、internalCounterの現在値を出力するようにしました(MSDNのサンプルにはありませんでしたが、確認用に僕が追加しました)。

Countdownクラスを改良する

MSDNではここまでしか書かれていないので、あとは
https://msdn.microsoft.com/ja-jp/library/9aackb16(v=vs.110).aspx
を参考にしつつ自己流で実装してみます。

まずCountdownクラスにコンストラクタを追加します。

これでCountdownクラスのインスタンス生成時に内部カウンタの値を設定できるようになりました。
簡単ですね。

コンソールアプリでCountdownクラスを利用する

コンソールアプリを作成すると、以下のようなコードがデフォルトで書かれてあるかなと思います。
ここに処理を追加していってみましょう。

具体的な実装は以下の通りです。

1.Mainメソッド内でCountdownのインスタンスを作成する
2.イベントハンドラを登録する
3.キーを押すたびにCountdownクラスの内部カウンタを減らす(Decrementメソッドの実行)

ではさっそくMainメソッド内に書いていきましょう。

+=演算子でイベントハンドラを登録しているのがわかりますね(コンソール画面にナンジャラホイと表示してるだけ)。
-=を使えばイベントハンドラの削除です。
今回はラムダ式(ツクラーにわかりやすい言い方をするなら無名関数。アロー関数とほぼ一緒です)を使いましたが、通常のメソッドも当然登録できます。
というか、イベントが出た当初はラムダ式なんてなかったので当然ですね。
また、イベントの実態はデリゲートなので、マルチキャストデリゲートもできます(マルチキャストイベントか?w)。

イベントハンドラの登録後、「aキーを押してカウンタを減らしてね。」という文字をコンソール画面に表示させています。
その後はwhileループを使い、aキーが押されるたびにc.Decrement()が実行されるようにしました。

実行結果はこんな感じ。

イベントの使い道

さて結構ややこしい下準備が必要なイベントですが、実際のプログラミングではどのような場面で使えばよいのでしょうか?
例えば僕が制作中のタイピングゲームでは、一連の文字入力が全て完了したときにイベントを発火させたり、タイプするターゲットが変わったときにイベントを発火させたりしています。

まあ僕もまだまだイベントを使いこなせていません。
この記事を書くまでEventHandler型すら知らなかったのですが(いやホントに。Action型を使っていました)、推奨されているのはEventHandler型のようなので、これからはEventHandler型を使っていこうと思います。
また、実際の開発中にも「あれ? こういうときイベントはどうやって発火すんの?」という疑問も尽きません。
ゲームプログラムとイベントの相性はよい気がするので、これからもっとうまく使えるようになれたらなあと思います。

イベントについてより詳しく知りたい方は
http://ufcpp.net/study/csharp/sp_event.html
ここ見たらいいんじゃないでしょーか。

ほなそんな感じで、またね。

フォローする