var a = new A(); a.Do(); var b = new B(a); b.Do(); // 後片付け b.Cleanup(); a.Cleanup();
ところが実際には、A.DoにしろB.Doにしろ、省略しているそれ以外のところでも、例外がスローされる可能性がありました。コンストラクト済みのA、Bの両オブジェクトは、Cleanupメソッドを呼ぶ必要があり、例外がスローされた場合も正しく処理されるためには、以下のようにコードを修正する必要がありました。
A a = null; B b = null; try { a = new A(); a.Do(); b = new B(a); b.Do(); } finally { if (b != null) b.Cleanup(); if (a != null) a.Cleanup(); }
結果として、A、Bいずれも宣言と同時に初期化ができなくなり、finally句内の後片付けも、構築済みかどうかのチェックが必要になってしまいました。
実際、この手のコードはたまに見かけます。正しく書くとこうなってしまうケース。でも、正直カッコワルいよね。ちなみに、このケースならA,Bの両クラスがIDisposableを実装して、DisposeでCleanupできれば問題ないんだけど、そうもいかないケースも結構多いですよね。
で、考えてみたのが「それなら、IDisposableの汎用クラスを作ってしまえば良いんじゃないか」ということ。試しに作ってみたのは、こんなどシンプルなクラス。クラス名は「Disposeするヒト」なので、「Disposer」クラス。
public class Disposer : IDisposable { private readonly Action disposer; public Disposer(Action disposer) { this.disposer = disposer; } public void Dispose() { disposer(); } }
これを作っておくことで、先のコードはこう変わります。
var a = new A(); using (new Disposer(() => a.Cleanup())) { a.Do(); var b = new B(a); using (new Disposer(() => b.Cleanup())) { b.Do(); } }
usingのスコープを抜けたときに行う処理を、あらかじめ定義しておくイメージでしょうか。ちなみに、using句でnewされたオブジェクトが特に参照されないのであれば、変数に代入しなくてもちゃんと動くんですね。初めて知った。