このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
今日は昨日のステップをもう少し進めてみる。
switch の中が明確になり、
果たすべき役割が複数の関数に分解された。
GetValue メソッドの一部を引用する。
switch (pscd.pwszExt.ToLower()) {
case ".lnk":
return GetShortcutTarget(pscd.wszFile);
case ".url":
return GetInternetShortcutTarget(pscd.wszFile);
default:
return GetOtherTarget();
}
コードを関数に移動しただけなので、
基本的には何も変わっていない。
コードを単純にルーチンに分割するだけなら、
これ以上は分解できないほどシンプルになった。
ここで、少し視点を変えてみよう。
「見た目」がすっきりしたので見えてくることがある。
よく見ると、switch 内のコードは、
全て「単一のメソッド呼び出し」となっている。
この「メソッドを呼び出す」という意味で見れば、
switch の各 case は共通の処理をしているということだ。
この「呼び出し」を switch の外に出せないだろうか。
つまり、「メソッド呼び出しを集約する」ことを考える。
case や default の中ではメソッドを呼び出さず、
「後で呼び出す予定」としてメソッドを記憶しておく。
そして、switch の後で、呼び出しを行うのだ。
C# には、デリゲートと呼ばれる特殊な変数があり、
この変数には、メソッドへの参照を格納することができる。
メソッドを呼んで戻り値を格納するのではない。
ただ、通常変数に型があるのと同じように、
デリゲート変数にも型がある。
デリゲート変数が格納できるのはメソッドなので、
言い換えれば「メソッドに型がある」ということだ。
メソッドの型とは、メソッドのシグネチャのことである。
シグネチャとは、戻り値と引数の型の組み合わせの事だ。
百聞は一見に如かず。コードを書いて説明しよう。
まず、デリゲートを宣言する。デリゲートとは
「特定の型のメソッドへの参照を保持」できる型であり、
enum や class などと同じ空間で宣言することができる。
delegate string LinkTargetHandler(string path);
書き方はメソッドの定義に似ている。
違いは、delegate というキーワードが入ることだ。
通常のメソッドだと、メソッド名のように見える部分が、
デリゲート宣言の場合、デリゲート型の名前となる。
上記では戻り値に string を返し、引数に string を取る、
シグネチャ持つメソッドを格納できる、
LinkTargetHandler という名前の型を定義した。
デリゲートには Handler を末尾につけるのが習慣のようだ。
さて、このデリゲートは型なので、
変数のように扱うことができる。
(暗黙に Delegate クラスから派生した参照型となる)
LinkTargetHandler hander = null;
そして、このデリゲート型のインスタンスを作成する。
hander = new LinkTargetHandler(GetShortcutTarget);
LinkTargetHandler のコンストラクタは、
デリゲートと同じシグネチャを持つ、
「メソッドへの参照」を引数として受け取る。
ポイントは、「GetShortcutTarget」だ。
括弧が付いてないのでこれはメソッド呼び出しではなく、
メソッド自身への参照を指す。
これによって、LinkTargetHandler インスタンスに、
GetShortcutTarget メソッドへの参照を渡す。
デリゲート経由でメソッドを呼び出すのは簡単である。
string target = hander("引数");
デリゲート変数に括弧をつけて呼び出せば、
デリゲート変数が参照するメソッドが呼び出される。
ではこれらを使って switch 文を書き換えてみよう。
デリゲートには型があるので、
switch 内で呼び出してるメソッドの型は
共通化しなければならない。
そこで、GetOtherTarget 型に余分な引数を追加し、
メソッドの型をあわせることにしよう。
static private string GetOtherTarget(string path)
そして、デリゲートを宣言する。
LinkTargetColumn でしか使わないので、
LinkTargetColumn のフィールドと同じ階層で宣言しよう。
private アクセス修飾子をつけること。
private delegate string LinkTargetHandler(string path);
そして、switch 文の中を書き換える。
// デリゲートを用意
LinkTargetHandler hander = null;
// 拡張子を調べる
switch (pscd.pwszExt.ToLower()) {
case ".lnk":
hander = new LinkTargetHandler(GetShortcutTarget);
break;
case ".url":
hander = new LinkTargetHandler(GetInternetShortcutTarget);
break;
default:
hander = new LinkTargetHandler(GetOtherTarget);
break;
}
return hander(pscd.wszFile);
どうだろうか。はっきり言って、手間が増えただけだと
感じるかもしれない。実際、俺もそう思う。
しかしながら、呼び出しを一箇所に集約することができた。
実際の処理が行われるのは、メソッド呼び出した時であり、
switch 文の中ではロジック自体は実行されない。
これは次のステップに進むにあたって重要な意味をもつのだ。
デリゲートは、C の関数ポインタと異なり、
関数への参照を直接格納する値型ではない。
関数への参照を内部に格納しているクラス型であり、
決して単純なクラスではないのだ。
デリゲートはまだまだ高度な機能を持っている
静的メソッドだけでなく、メンバメソッドを呼んだり、
複数のメソッドを連鎖的に呼び出したりする機能も持つ。
このあたりは、イベントやリスナーといった、
UI 系のプログラミングで多用されている。