TIPS:確保したリソースを忘れずに解放するには?[C#/VB]プログラム実行時に確保したリソースは忘れずに解放する必要がある。usingステートメント/Disposeパターンを使って、これを確実に行う方法を紹介する。});連載目次リソースとは、メモリやファイル、あるいはデバイスコンテキストやウィンドウハンドルなどといった、プログラムの外にあるおよそあらゆる全てのものだ。それらをプログラムで利用するために確保した場合は、(一部の例外を除き)プログラムで解放する必要がある。解放しないと、プログラムのメモリ使用量は増大し続けるし、排他的に確保しているリソースは他から利用できないままとなる。Frameworkが管理しているメモリは例外だ。使われなくなったオブジェクトに割り当てられているメモリは、ガベージコレクタ機構によって自動的に解放される。確保したリソースを忘れずに解放するにはusingステートメントかDisposeパターンを使えばよい。本稿では、それらの使い方を解説するとともに、Disposeメソッドの振る舞いを調べてみる。特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。2015以降が必要である。サンプルコードはコンソールアプリの一部であり、以下の名前空間の宣言が必要となる。FrameworkのクラスでDisposeメソッドのあるものは、解放すべきリソースを持っている。Disposeメソッドを呼び出すと、そのオブジェクトが確保しているリソースが解放される。そのようなクラスは、usingステートメントを使うことで確実にリソースを解放できる(次のコード)。ここでFileStreamオブジェクトのDisposeメソッドが呼び出されるusingステートメントを使ってリソースを解放する例(上:C#、下:VB)usingステートメントのブロックを抜けるときに自動的にDisposeメソッドが呼び出される(後ほど、実際に確認する)。そのため、Disposeメソッドを呼び出すコードは不要である。FileStreamクラス/TextWriterクラス(ともにSystem.IO名前空間)は、Disposeメソッドを呼び出すとクローズされるので、Closeメソッドも呼び出さなくてよい。ちなみに、UWPアプリ用のAPI(例えばFileOutputStreamクラスなど)では、Closeメソッドは廃止されている。C#では、複数のusingステートメントを連続させるときに、途中の中かっこを省略できる(次のコード)。Encoding.UTF8)){writer.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff"));}C#ではusingステートメントが連続するときに途中の中かっこを省略できる(C#)このように書けば、インデントが深くならずに済む。usingステートメントは、そのブロックから抜け出すときにDisposeメソッドを呼び出してくれる。ブロック内で例外が発生したとしてもである。同等のコードをusingステートメントを使わずに書くと、次のコードのようになる。例外が発生してもDisposeメソッドを呼び出せるように、try〜finallyステートメントを使っている。Tryusingステートメントを使わずにリソースを解放する例(上:C#、下:VB)例外に対処するため複雑なコードになってしまう。Finallyブロック内に登場している「?」記号は、Null条件演算子である。オブジェクトが生成される前に例外が出た場合を考慮して、オブジェクトがnullのときはDisposeメソッドを呼び出さないようにしている。2015以前では、nullチェックを行うif文に書き換えてほしい。メンバ変数に確保したリソースをDisposeパターンで解放するには?確保したリソースを繰り返し利用するためにクラスのメンバ変数に保持することがある。この場合はusingステートメントが使えない。確実にリソースを解放するにはどうしたらよいだろうか?そのようなクラスには、IDisposableインタフェース(System名前空間)を実装すればよい。「お約束」があり、Disposeパターンと呼ばれている。IDisposableインタフェースを実装したクラスは、usingステートメントで利用できる。Studioでは自動生成できる。2015では、「IDisposable」と書いたらそこで「電球」アイコンをクリックすると、Disposeパターンの生成を選択できる(次の画像)。クラスを宣言し、継承するインタフェースとして「IDisposable」と書いたら、そこにマウスカーソルをホバーさせるかキー入力キャレットを置くと、電球のアイコンが表示される。そこで電球のアイコンをクリックするかCtrl+.キーを押すと、画像のようなメニューが出てくるので、Disposeパターンの実装を選ぶ。パターンを使ってインターフェイスを実装します]を選択した場合のもの)。2015での例である。上の画像のDisposeパターンのスケルトンで、コメントに「TODO」とある部分を実装していけばよい。ここではリソースを解放する代わりに、実験用としてコンソールに文字列を出力する例を示そう(次のコード)。メンバ変数に保持している場合は、ここでDisposeする。Frameworkのオブジェクトを破棄しました。メンバ変数に保持している場合は、ここで解放する。WriteLine("■アンマネージリソースを解放しました。ファイナライザーをオーバーライドします。このコードを変更しないでください。このコードは、破棄可能なパターンを正しく実装できるように追加されました。このコードを変更しないでください。次の行のコメントを解除してください。Disposeした後はファイナライザーが呼び出されないようにする。メンバ変数に保持している場合は、ここでDisposeする。Frameworkのオブジェクトを破棄しました。メンバ変数に保持している場合は、ここで解放する。WriteLine("■アンマネージリソースを解放しました。オーバーライドします。このコードを変更しないでください。よって追加されました。このコードを変更しないでください。次の行のコメントを解除してください。Disposeした後はファイナライザーが呼び出されないようにする。Class実験用にDisposeパターンを実装したクラス(上:C#、下:VB)リソースを解放する代わりに、コンソールに文字列を出力するようにした(太字の部分)。APIを呼び出して取得したハンドルなど)の解放を行う。複雑なコードだが、自動生成されたスケルトンのコメントに従って何カ所かコメントを外し、太字にした部分のコードを記述しただけである。このDisposableSampleクラスは、以降のサンプルコードで利用する。上のDisposableSampleクラスを使ってみよう。明示的にDisposeメソッドを呼び出してみる(次のコード)。Frameworkのオブジェクトを破棄しました。■アンマネージリソースを解放しました。Frameworkのオブジェクトを破棄しました。■アンマネージリソースを解放しました。Dispose後DisposableSampleクラスのDisposeメソッドを明示的に呼び出す(上:C#、下:VB)上のコードの出力を見ると、Disposeメソッドの呼び出しによって、マネージリソースとアンマネージリソースが順に解放されている。次は、usingステートメントを使ってみる(次のコード)。上のコードと同じようにリソースの解放が実行されている。確かに、usingステートメントによって自動的にDisposeメソッドが呼び出されているのである。Frameworkのオブジェクトを破棄しました。■アンマネージリソースを解放しました。Frameworkのオブジェクトを破棄しました。■アンマネージリソースを解放しました。正しく1回だけDisposeメソッドを呼び出すべきではあるが、実際どうなるのか前述のDisposableSampleクラスを使って確かめてみよう。繰り返してDisposeメソッドを呼び出してしまった場合だ(次のコード)。Frameworkのオブジェクトを破棄しました。■アンマネージリソースを解放しました。Frameworkのオブジェクトを破棄しました。■アンマネージリソースを解放しました。Disposeを重複して呼び出しても何も起きないDisposableSampleクラスのDisposeメソッドを繰り返して呼び出す(上:C#、下:VB)このようなコードを書くべきではないが、もしも間違えて書いてしまっても、Disposeパターンがきちんと実装されたオブジェクトならば問題ない。Disposeパターンが正しく実装されているオブジェクトなら、Disposeメソッドを重複して呼び出してしまっても問題ない。Disposeメソッドを呼び出し忘れるとどうなる?次に、Disposeメソッドの呼び出しを忘れてしまった場合だ(次のコード)。オブジェクトはいつか参照されなくなり、ガベージコレクタによっていつかは破棄される。このコードではそれをシミュレートするために、変数にnullをセットしてからガベージコレクタを強制的に動かしている。■アンマネージリソースを解放しました。■アンマネージリソースを解放しました。(注意)GCは非同期で実行されるため、最後の2行は入れ替わることがあるDisposeメソッドを呼び出し忘れた場合のシミュレーション(上:C#、下:VB)呼び出し忘れても、Disposeパターンがきちんと実装されていれば、ガベージコレクタ機構によってリソースは解放される。オブジェクトの存在時間が長くなる分だけ、解放される前にアプリが異常終了してしまって解放されなくなるという可能性も高くなる。上の出力例を見ると、Disposeメソッドの呼び出しを忘れても、(Disposeパターンがきちんと実装されていれば)アンマネージリソースは解放される。マネージリソースはDisposeパターン内では解放されていないものの、マネージリソースを持つオブジェクトへの参照はなくなっているので、ガベージコレクタによって自動的に解放される。Disposeメソッドの呼び出しを忘れても、リソースは解放されるのだ。Disposeメソッドを呼び出さなくても問題はないのかというと、そんなことはない。リソースを抱えたままでオブジェクトが生存し続ける時間が長くなってしまうのだ。それはひょっとするとアプリ終了時まで続くかもしれない。その間、排他的に確保したリソースは他から利用できない。ガベージコレクタによって破棄される前にアプリが異常終了してしまい、リソースが解放されなくなるという可能性も時間とともに増えるのだ。それと、Disposeメソッドを呼び出さなかった場合はファイナライザーよって解放処理が実行されるのだが、ファイナライザーを呼び出す処理自体のコストは高い(時間がかかる)。ガベージコレクションが実行されたときの負荷が上がるということである。Disposeパターンがきちんと実装されていない場合は、Disposeメソッドを重複して呼び出したり、呼び出しを忘れたりすると、不具合を起こす可能性があることにも注意してほしい。そのようなオブジェクトでは、2回目のDisposeメソッド呼び出しで例外が出たり、ガベージコレクタによって破棄されるときにアンマネージリソースが解放されなかったりするのだ。メールマガジンをご購読ください。申し込みページへITmediaはアイティメディア株式会社の登録商標です。