このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
デリゲートを使うと呼び出しを集約することができた。
昨日のコードを再掲しよう。
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);
デリゲートを使った場合、
まずはデリゲート型変数にメソッドを登録し、
後で変数経由でそれを呼び出した。
いわば、メソッド呼び出しを遅延し、
呼び出すタイミングを共通化することに成功したわけだ。
しかし、不満点もある。GetOtherTarget メソッドの
シグネチャを変更し、本来は不要である引数を
追加する必要があったからだ。
昨日使ったデリゲートは、戻り値に string を返し、
引数に string を取るという型であった。
delegate string LinkTargetHandler(string path);
デリゲートは、型に対する強い制約があるため、
登録するメソッドのシグネチャを統一する必要がある。
これを回避するためにはどうすればよいか。
今回デリゲート変数経由で呼び出される処理を考えてみよう。
1. 変数が保持するメソッドに引数を渡す。
2. メソッドが内部で処理をする。
3. メソッドが返す結果を受け取る。
使っているメソッドは、全て外部に依存しない
関数型のメソッド(引数以外に依存しない)であるため、
当たり前のことだが、メソッドを呼び出す場合、
「呼び出した時点」で上記の処理を「全てその場で」行う。
呼び出し側の視点で考えれば、1 が初期化、3 が結果だ。
初期化というのは、扱うものによって作法が異なるため、
メソッド呼び出しから分離することを考える。
こういった処理は、言語によっては
クロージャを使って実現できるのだが、
C# では、軽量のクラスでラップして作る。
初期化と呼び出し(利用)を分けるということは、
初期化がコンストラクタに該当し、
利用がメンバメソッド呼び出しとなるわけだ。
そうすれば、メソッドを呼び出す時点で引数は不要となる。
では、それぞれをクラスに分けてみよう。
あくまでも、無理やりクラス化しただけなので、
メソッド内の処理は変わらない。なので省略する。
========== ShortcutFile.cs ==========
using System;
using System.Text;
using System.Runtime.InteropServices;
using LoaferShellEx.Interop;
namespace LoaferShellEx.Column {
public class ShortcutFile {
private string _path;
public ShortcutFile(string path) {
_path = path;
}
public string GetLinkTarget() {
// 省略。_path から読み込み string を返す。
}
}
}
========== end of ShortcutFile.cs ==========
========== InternetShortcutFile.cs ==========
using System;
using System.Runtime.InteropServices;
using LoaferShellEx.Interop;
namespace LoaferShellEx.Column {
public class InternetShortcutFile {
private string _path = null;
public InternetShortcutFile(string path) {
_path = path;
}
public string GetInternetShortcutTarget() {
// 省略。_path から読み込み string を返す。
}
}
}
========== end of InternetShortcutFile.cs ==========
========== NullShortcut.cs ==========
using System;
using LoaferShellEx.Interop;
namespace LoaferShellEx.Column {
public class NullShortcut {
public NullShortcut() {
}
public string GetOtherTarget() {
throw new ComFalseException();
}
}
}
========== end of NullShortcut.cs ==========
このようにすると、GetOtherTarget に引数が要らなくなる。
では、これを利用するコードを書いてみよう。
デリゲート変数には、静的メソッド(関数等)だけでなく、
メンバメソッドへの参照も格納することができ、
後でインスタンスのメソッドを呼び出すこともできるのだ。
以前は初期化として必要だった引数は不要となり、
デリゲートの宣言は以下のようになる。
private delegate string LinkTargetHandler();
続いて、LinkTargetColumn の GetValue メソッドだ。
// デリゲートを用意
LinkTargetHandler hander = null;
// 拡張子を調べる
switch (pscd.pwszExt.ToLower()) {
case ".lnk":
hander = new LinkTargetHandler(
new ShortcutFile(pscd.wszFile).GetLinkTarget);
break;
case ".url":
hander = new LinkTargetHandler(
new InternetShortcutFile(pscd.wszFile).GetInternetShortcutTarget);
break;
default:
hander = new LinkTargetHandler(
new NullShortcut().GetOtherTarget);
break;
}
return hander();
デリゲート変数への代入は不思議な感じであり、
インスタンスのメソッドを呼び出すのと同じ構文で、
括弧を省略することでメソッドへの参照を渡す。
デリゲート変数がインスタンスへの参照を保持するので、
new LinkTargetHandler(new NullShortcut().GetOtherTarget);
なんて書き方ができるのだ。
これで、メソッドへの余分な引数の追加はなくなった。
ただし、昨日よりさらに複雑になった(笑)
明日は、これらをもう少し見やすく書き換えよう。
# 題材があまりよくないので、
# 昨日や今日行った分割には、それほどメリットはない。
# 出来る人がみれば、お前何やってんだと思うかも。
# ここまで読んできた人はもうお分かりだと思うが、
# これらは、明日ある内容を説明するための布石なだけだ。