よくわかってなかったcacaponがまとめたC#のデリゲートの作り方
二次元配列の各要素に対して、処理を行いたいとき、
簡単に思いつく形と言えば、こんな形になるのかなと思います。
for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { //何らかの処理 } }
一つだけなら、これの中に処理を入れればいいのですが、
その要素に対して行いたい処理が複数ある場合、どうしたらよいでしょう??
例えば、私はパズル要素があるゲームを作成中なのですが、
二次元配列に対して、以下の処理をしたいという要望がありました。
- すべての配列の要素を、初期値で初期化したい。
- パズルのピースデータを動かす用の配列にセットしたい。
- 二つの同じ大きさの配列を比べて、重複していなかったら一方の配列に追加したい。
- etc...
これすべてに前述の二重のfor文を記載するのも一つの手なのでしょうが、
同じものを何度も書くのはミスの始まりですし、何よりまとめられるならまとめたい。
そう思ったのです。
ただ、今回はInterfaceのように他の所から呼び出すわけじゃなく、
クラス内で完結しています。それに対するかき方ってあるのでしょうか?
と思い、調べたところ、デリゲートという機能を使うと実現できそうという事が分かりました。
今回は、そのデリゲートについて、cacaponなりに理解した内容をまとめてみたいと思います。
こんな感じで実装してみました。
説明
ココでは三つの関数を作成しました。
private void InitErea((Guid, ePieceColor)[,] erea)
private void Search(int height,int width, Action<int,int> action)
public void SetHoldErea((Guid,ePieceColor)[,] piece)
このうち、二次元配列の各要素を順番に見ていく部分をSearchとして定義し、
内部の処理に関しては、各呼び出し側で作成して、それを入れるような形で表現しています。
例えば、InitEreaでは erea[height, width] = (Guid.Empty, ePieceColor.NONE);
という処理を行わせている感じですね。
ちょっと処理が分かりにくいと思うので、InitEreaの動きを見てみましょう。
- InitEreaを呼びだす。
- Searchを実行するために、引数として高さ、幅、行いたい処理を渡す。
この時の中の処理が(height,width)=>{erea[height, width] = (Guid.Empty, ePieceColor.NONE);})
です。 - Searchが実行され、二重のfor文の内部、各要素毎にaction(h,v);が呼ばれます。
2重のFor文で二次元配列にアクセスするイメージは、下記のような感じです。
要素h0w0の時はaction(0,0)が呼ばれ、
つまり、(0,0)=>{erea[0, 0] = (Guid.Empty, ePieceColor.NONE);}
の処理が行われる感じですね。
h1w3なら(1,3)=>{erea[1, 3] = (Guid.Empty, ePieceColor.NONE);}
になります。
デリゲートの前にラムダ式について
ここで当たり前のように出てきた、 () => {何かの処理;}
は慣れていないと分かりにくいですよね。
私もハマったところなのですが、これはラムダ式と言って、名前を定義しなくても使える関数になります。
例えば、上述の(height,width)=>{erea[height, width] = (Guid.Empty, ePieceColor.NONE);})
は
private void 名無しの関数(int height, int width) { erea[height, width] = (Guid.Empty, ePieceColor.NONE); }
というのと実質的には同じになるようです。
デリゲートでは、処理の内容をラムダ式で記載することが多いようで、
実質セットとして扱われています。
最近良く使われているのは下記の四つのデリゲートらしいです
C#で使われるデリゲートは何種類もあるらしいのですが、
最近使われているのは下記4種類が多いようです。
- Action
引数無し、戻り値なし - Action<引数1の型(,...引数Nの型)>
引数有り、戻り値なし - Func<戻り値の型>
引数無し、戻り値有り - Func<引数1の型(,...引数Nの型),戻り値の型>
引数有り、戻り値有り
私自身も理解するうえで、上の形式が理解しやすかったため、
上記の定義方法だけ覚えています。
今回の例では、
private void Search(int height,int width, Action<int,int> action)
の三番目の引数が 引数有り、戻り値なしの関数として使いたかったため、
Action<int,int>型のデリゲート actionを使用しています。
まとめ
今回の二重ループの中身だけ処理を変えたい!
のような、途中まで同じ処理なのに、一部だけ変えたい場合、
デリゲートによる実装は便利だと思います。
引数や戻り値の有無で使えるデリゲートは変わりますが、
そこさえ分かれば、変数のように
関数を代入することができるようになるのは
実装の幅が広がるのではないかと私は思います。
こんかいはここまで、また会いましょう。