ものすごく抽象的になりますが、おおよそこんなコードがありました。
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されたオブジェクトが特に参照されないのであれば、変数に代入しなくてもちゃんと動くんですね。初めて知った。