どうも、Doo(@doo11gms)です。
Unity無料化から時が経ち、C#も人気言語ランキングで上位にランクインするほどの人気言語になりました。
そんなC#ですが、大抵の人が最初に躓くのはデリゲートではないでしょうか。
自分は何度解説を読んでも理解できず、すっ飛ばしたのを覚えています。
デリゲートは無能か?
いいえ、デリゲートは超が付くほど優秀な機能です。
デリゲートを使わないと実装できないような機能も存在します。
メソッドを変数のように扱ったり、引数にメソッドを渡したり、とにかく多彩なロジックが実現可能です。
一見ちょっと奇妙な構文ですが、意味が分かると「なんだそれだけの事か」となるのがデリゲートです。
是非諦めずにトライしてみて下さい。
デリゲートは奇妙な構文?
突然ですが、次のコードを見て下さい。
1 |
delegate void MyType(); |
キモいです。実にキモいですdelegate。
でも、それが正常な反応なのでご安心を。
では、次のコードはどうでしょうか?
1 |
int x; |
普通のint型変数xの宣言ですよね。
何か変数を宣言するときは、
型名 変数名;
とすれば良い訳ですね。
「何を当たり前の事を」と思われたかもしれませんが、
それでは「変数を宣言」ではなく「メソッドを宣言」するにはどうしたら良いでしょうか(ここで言う宣言とは、実装を伴わないプロトタイプ宣言のことを指します)。
変数宣言は「型名 変数名」と書きましたので、同じように
型名 メソッド名;
と書けば良さそうです。
…しかし、メソッドを表す型ってなんでしょうか?
変数を表す型なら簡単です。
変数が整数ならint型ですし、浮動小数点ならfloat型やdouble型ですよね。
実は「メソッドを表す型」がパッと浮かばないのは、それが無数に存在するからです。
では、具体例を挙げてみましょう。
・void型を引数に取り、void型を返すメソッド
・int型を引数に取り、int型を返すメソッド
・float型とfloat型を引数に取り、int型を返すメソッド
…どうでしょうか?
御覧の通り、”メソッドの型”というものは無数に存在するので、intやfloatのように、1つ1つに予約語を割り当てることができないのです。
これは非常に困った問題です。
型名を指定できなければ、変数宣言のような形式でメソッドを宣言できません。
解決策:必要な型だけ自分で作ればいい。
メソッドの型が無数に存在するといっても、1つのプロジェクトで使うメソッド型は有限ですよね。
更に言えば、「メソッドを(プロトタイプ)宣言しなければならない状況」は更に少ないはずです。
だったら、「自分で必要なメソッド型だけ定義するようにしよう」というのがデリゲートです。
例えば「void型を引数に取り、void型を返すメソッド型をMyType型と定義する」のであれば、
1 |
delegate void MyType(); |
と書くことが出来ます。
MyTypeの部分が型名なので、ここは自分の好きな型名に変更して下さい。
他にもいくつか例を挙げておきましょう。
・int型を引数に取り、int型を返すメソッド型”MyType”型を定義する
1 |
delegate int MyType(int); |
・int型とint型を引数に取り、void型を返すメソッド型”MyType”型を定義する
1 |
delegate void MyType(int, int); |
・float型とfloat型を引数に取り、bool型を返すメソッド型”MyType”型を定義する
1 |
delegate bool MyType(float, float); |
一般的に、delegateキーワードを用いてメソッド型を定義する方法は以下の通りです。
delegate 戻り値の型 メソッド型名(引数の型, …);
最初は戸惑うかもしれませんが、慣れると本当に自然な書き方に見えてきます。
この時点でよく分からなくても心配しないで下さい。
メソッド型を定義しているだけですので、まだ何の旨味もありません。
現時点ではただの謎の構文に過ぎませんが、次の話で全貌が見えてくるはずです。
メソッドを宣言する
それでは、スタートに戻りましょう。
変数は、以下のように「型名 変数名」という形式で宣言することができました。
1 |
int x; |
同様に、「型名 メソッド名」という形式でメソッドを宣言してみます。
なお、今回は例として「void型を引数に取り、void型を返す”MyType”型」を型名に使います。
1 |
delegate void MyType(); |
続けて、メソッドXを宣言します。
1 |
MyType X; |
どうでしょうか?
デリゲートを使うことで、メソッドも変数と同じように「型名 名前」の構文で宣言できてしまうのです。
で、何が嬉しいの?
…と言いたくなる気持ちはよく分かります。
デリゲートを使うことで、メソッドを宣言することができるようになりました。
デリゲートの凄さは、その活用方法にあります。
実は、デリゲートを利用することでメソッドを変数のように扱うことができるようになります。
1 |
MyType X; |
このコード、見た目はint xのような変数宣言と一緒の形式ですよね。
実際、変数とほぼ同じ扱いをすることができるので、プログラムの途中でXに別のメソッドを代入できますし、他のメソッドの引数にメソッドXを渡して実行してもらう、なんてこともできちゃいます。
一つ覚えておかなければいけないのが、Xの実行方法です。
Xに格納されているのはメソッドですので、ただの変数と異なり実行することができます。
1 |
X.Invoke(); |
これでXに格納されているメソッドを実行することができます。
デリゲートの話はこれだけなのですが、慣れるまで中々気味の悪い機能であることは確かです。
ただ、デリゲートが使いこなせれば、実装できるロジックの幅が一気に広がります。
特に、「●●秒待ってから実行」などの非同期処理をエレガントに記述することができます。
デリゲートを利用したサンプルコードを2つ用意したので、ご参考下さい。
デリゲートのサンプルコード
※↓解説用のコード、一番単純なデリゲートの使い方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
delegate void MyType(); MyType X; void Method1() { // 何か1 } void Method2() { // 何か2 } void Entry() { X = Method1; X.Invoke(); // => 何か1 X = Method2; X.Invoke(); // => 何か2 } |
※↓実際にはこんな感じで非同期処理などに使います。デリゲートを使えば、引数にメソッドを渡すことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
class DelayedLauncher { public delegate void DelayedLaunchMethod(); DelayedLaunchMethod m_DelayedLaunchMethod; public void DelayedLaunch(DelayedLaunchMethod Target, float delay) { // delay秒待つ処理 Target.Invoke(); } } class Entry { void Entry { DelayedLauncher delayedLauncher = new DelayedLauncher(); delayedLauncher.DelayedLaunch(PlaySound(), 1.0f); // 1.0f秒後に音が鳴る delayedLauncher.DelayedLaunch(PlayEffect(), 1.5f); // 1.5f秒後にエフェクトが表示される } void PlaySound() { // 音を鳴らす } void PlayEffect() { // エフェクトを表示する } } |
コメント