VB.NETでもアンマネージドリソースを解放したい

はじめに

今回はアンマネージリソースを抱えるクラスの設計の話ではなく、アンマネージリスースを含むクラスを使い終わって破棄するときのお話です。

ちょっと前にこんなコードを見かけました。

Function Hoge() as Integer
    Dim connection = New SqlConnection("...")
    connection.Open()
    ' なんかの処理
    Dim result = expression
    Return result
    connection.Dispose()
End Function

何が悪いのでしょう。 セオリー通りに開いて閉じてます。

  1. 接続を開いて
  2. いろいろやりたいことをやって
  3. 値を返して
  4. 接続を閉じて・・・?

閉じれてない? 閉じてない!!

Return resultで制御が呼び出し元に戻り、そのままconnectionが指し示すインスタンスは均一なるマトリクスの裂け目の向こうに行ってしまいます。 そしてガベージコレクタによってインスタンスがファイナライザキューにブッ込まれ、ガベージコレクタがインスタンスのファイナライザを呼び出すまでリソースは解放されないままです。

いずれ解放されるならいいじゃないかと思うかもしれませんが、問題はガベージコレクタによってファイナライザが呼び出されるのがいつ分からないということです。すぐに解放されるかもしれないし、逆にアプリケーションが終了するまで解放されないかもしれません。

また、仮に値を返す前にconnection.Dispose()を呼び出すようにコードを書き直しても、処理の途中で例外はスローされた場合はDispose()が呼び出されないのでリソースが解放されないままファイナライザで処理されるまでどっかに行ってしまいます。やばいね

Using

どんなことがあっても確実にリソースを解放してやる必要があるのが我々生産性アプリケーション開発者です。

その辺なんかはUsingステートメントや例外ハンドラを使えばいい感じになんとかなるのですが、まぁUsingの使い方等々はmsdnをはじめいろんなところに書いてあるので今更書かないです。

Using ステートメント (Visual Basic) | Microsoft Docs

CloseとDispose

ここからはどっかに根拠があるっていう訳ではなく、弊社の考え的なものも含まれますので間違っててもごめんなさい。 コメントあたりに書いてもらえればなるはやで修正しますのでいい感じにお願いします。

さて、偉大なる指導者Google様の検索候補を見るとCloseDisposeの違いは何ぞやという検索が多いみたいです。

違いと言いますと

  • Dispose:Disposeパターンを用いて不要になったアンマネージリソースを解放する必要があるクラスがSystem.IDisposableを実装し、リソースを解放する時に呼び出すメソッド。実装しておけばUsingステートメントを利用できる。普通はこれでアンマネージリソースを解放するよねってやつ。
  • Close:特に上のようなインターフェースやルールなどは無いけどAPI設計によりリソースの解放とか出来ることもある。あとはリソースは全く関係ないたまたまCloseという名前が妥当なメソッド。

かなと思ってます。

ネット上を見てると

  • CloseCloseしたあともう一回Openを呼び出しインスタンスを使用することができる
  • インスタンスを破棄するときはCloseを呼び出したあとにDisposeを呼び出さないといけない

とか書いてあることがあります。 確かにそういうクラスも探したらあるのかもしれませんし、そういう風に実装することもできますが、そのような規則があるっていうmsdnは見たことがありません。

例えばSystem.Text.StreamWriterCloseメソッドは内部でDisposeを利用しているので呼び出しの意味は等価になりますし、System.Data.SqlClient.SqlConnectionCloseDisposeは機能的には同じとされています。

StreamWriter.Close メソッド (System.IO)

SqlConnection.Close メソッド (System.Data.SqlClient)

どうもBCL*1自体の実装もはっきりと一貫性を持って実装されていないのでどのクラスにも適用できる最適な解はないというのが個人的な答えです。 詳細はドキュメントを参考にして個別に対応するしかないと思ってます。

まぁ、ドキュメントを参考にしろで終わるとなんかアレなので、個人的にこんな感じで判断してますよってのをひとつ。

  • リソースを解放するために呼び出すメソッドはドキュメントを読んで判断
  • でもまぁIDisposableが実装されていれば普通に考えればこれを呼び出せばokなハズだよね
  • Closeを呼び出した後Openを呼べるのもあるかもしれないけどDisposeを呼び出した後Openを呼べるのはまず無い
  • でもまぁCloseは大抵Disposeと同義なことが多いからClose後にOpenするのはドキュメントに明示されていない限りやらない
    • 明示されててもやりたくない
  • Dispose後のインスタンスはまず使えない
    • 使えるのは設計的にどうかと
  • 例外をスローする可能性のあるリソースは例外ハンドラやUsingステートメントを利用して確実に解放できるようにする

コンパイラや処理系でもさすがにデザインパターンに準じてるかどうかの判断や強制はできないので『まず』とか『基本的に』なんて言葉を付け加えないと怖い人から怒られそうです。 いくらでもデザインパターンやセオリーを無視したクラスを設計できますからね。

余談ですがレガシーVBでは変数にNothingを代入すればCOMなんかのリソースが解放できたとかなんとかとありますが、VBにおいてはそんなことはないのでその辺を宜しくお願いします。 正確にはファイナライザが呼び出されれば一応解放はされるのですが、上でも書いた通りいつ呼び出されるのかが分からないのでファイナライザにリソース解放を任せるべきではありません。自分で明示的に解放しましょう。

おわり

*1:Base Class Library 標準クラスライブラリ