2016年8月1日月曜日

IDisposable 汎用クラスの実装

ものすごく抽象的になりますが、おおよそこんなコードがありました。

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されたオブジェクトが特に参照されないのであれば、変数に代入しなくてもちゃんと動くんですね。初めて知った。

もう8月ですね...

ほとんど更新しないまんまもう8月。月日の経つのはほんと早い。

いや、真田丸も面白いし、相変わらず漫画もたくさん読んでるし、ポケモンGOも始めたしで、書くことないわけでもないんですが、やっぱり本筋のC#/LINQ的なネタがないと、他のことも書こうという気になかなかならず...。

ちょっとだけネタができたので、ぼちぼち行きます。