C#/IDisposableへのwrapping のバックアップソース(No.2)

更新

[[公開メモ]]

#contents

* 必ず後処理の必要なクラスが IDisposable を実装していない?! [#ke4bae90]

やまねこ@楢ノ木技研 さんからのお題で、表記のような状況を解決する方法を考えてみました。

** IDisposable に wrap するためのライブラリを手軽に作る [#bd853fd5]

こんなのが思い浮かびました。

どうでしょうね。

 LANGUAGE:C#(linenumber)
 // Dispose 時に Close() したいのに IDisposable を継承してないクラスがいくつかある
 class ToBeClosed1
 {
     public void Close()
     {
         MessageBox.Show("Closed!");
     }
 }
 
 class ToBeClosed2
 {
     public void Close()
     {
         MessageBox.Show("Closed!");
     }
 }
 
 // using を使えるようにする wrapper
 class DisposableWrapper: IDisposable
 {
     Action action;
     protected DisposableWrapper(Action action)
     {
         this.action = action;
     }
     public void Dispose()
     {
         action.Invoke();
     }
     // ここに必要な変換を追加すれば・・・
     public static implicit operator DisposableWrapper(ToBeClosed1 tbc1)
     {
         return new DisposableWrapper(() => tbc1.Close());
     }
     public static implicit operator DisposableWrapper(ToBeClosed2 tbc2)
     {
         return new DisposableWrapper(() => tbc2.Close());
     }
 }
  
 // こうやって使えるようになります
 private void button1_Click(object sender, EventArgs e)
 {
     using ( (DisposableWrapper) new ToBeClosed1())
     {
     }
 
     using ( (DisposableWrapper) new ToBeClosed2())
     {
     }
 }

もちろん、必要に応じて 33 行目を

 LANGUAGE:C#
         return new DisposableWrapper( () => { 
             if ( ! tbc1.Closed ) tbc1.Close(); 
         });

などとすることも可能なので、この形で任意の後処理を行えます。

* 本来ならば [#ld426a96]

新たに ToBeClosed3 へ対応したいときに DisposableWrapper 
自体に変更を加えなくて済むようにしたいところ。

書きやすさは失われますが拡張メソッドを使えば次のようにすることもできます。

 LANGUAGE:C#(linenumber)
 // Dispose 時に Close() したいのに IDisposable を継承してないクラスがいくつかある
 public class ToBeClosed1
 {
     public void Close()
     {
         MessageBox.Show("Closed!");
     }
 }
 
 public class ToBeClosed2
 {
     public void Close()
     {
         MessageBox.Show("Closed!");
     }
 }
 
 // using を使えるようにする wrapper
 public class DisposableWrapper : IDisposable
 {
     Action action;
     public DisposableWrapper(Action action)
     {
         this.action = action;
     }
     public void Dispose()
     {
         action.Invoke();
     }
 }
 
 // wrapper への変換を拡張メソッドとして実装する
 
 public static class WrapToBeClosed1
 {
     public static DisposableWrapper AsDisposable(this ToBeClosed1 tbc1)
     {
         return new DisposableWrapper(() => tbc1.Close());
     }
 }
 
 public static class WrapToBeClosed2
 {
     public static DisposableWrapper AsDisposable(this ToBeClosed2 tbc2)
     {
         return new DisposableWrapper(() => tbc2.Close());
     }
 }
 
 // こうやって使えるようになります
 private void button1_Click(object sender, EventArgs e)
 {
     using ( (new ToBeClosed1()).AsDisposable() )
     {
     }
 
     using ( (new ToBeClosed2()).AsDisposable() )
     {
     }
 }

OOP 的にはこちらの方が正しいですが、
シンタックスシュガー的には上の方が良いかも?

* 変数のスコープ問題 [#o44fa085]

やまねこ@楢ノ木技研 さんの TW で気づかされたことには、
上記のような方法だと本当に使うときには以下のような不格好なことになってしまいます。

 LANGUAGE:C#(linenumber)
 // 実際にはこうやって使うことに
 private void button1_Click(object sender, EventArgs e)
 {
     ToBeClosed1 tbc1;
     using ( (tbc1 = new ToBeClosed1()).AsDisposable() )
     {
         // tbc1 を使う
     }
 }

これは不格好ですね。

以下のような方法もありますが、こちらはもっと不格好かも。

 LANGUAGE:C#(linenumber)
 // using を使えるようにする wrapper
 // 実際にオブジェクト自体を wrap する形にした
 public class DisposableWrapper<T> : IDisposable
 {
     // オブジェクト自体を保持する
     public T content { get; private set; }
     // 後処理に必要な手順
     Action action;
     // オブジェクト自身と、その後処理の組み合わせで初期化する
     public DisposableWrapper(T obj, Action action)
     {
         this.content = obj;
         this.action  = action;
     }
     // 後始末
     public void Dispose()
     {
         action.Invoke();
     }
     // 非明示的に T に変換可能
     public static implicit operator T(DisposableWrapper<T> wrapped)
     {
         return wrapped.content;
     }
 }
 
 // ここの記述がちょっとだけ面倒になったかも
 public static class WrapToBeClosed1
 {
     public static DisposableWrapper<ToBeClosed1> AsDisposable(this ToBeClosed1 tbc1)
     {
         return new DisposableWrapper<ToBeClosed1>(tbc1, () => tbc1.Close());
     }
 }
 
 // こうやって使う
 private void button1_Click(object sender, EventArgs e)
 {
     // ラップされたものをそのまま変数に受けておいて
     using (var wrapped = (new ToBeClosed1()).AsDisposable())
     {
         // 使うときに中身を取り出す
         wrapped.content.SomeMethod1();
 
         // 引数や代入で使うときは暗黙のキャストが働くのでそのまま使える
         SomeMethod2(wrapped);      // ToBeClosed1 を引数とする関数に渡す
         ToBeClosed1 obj = wrapped; // ToBeClosed1 型の変数へ代入
     }
 }

使いやすいとはお世辞にも言えない?

今のところ、これ以上の案は出てません(TT

* コメント [#q810f856]

#article_kcaptcha

Counter: 4582 (from 2010/06/03), today: 1, yesterday: 2