.NET Frameworkにはガーベジコレクタ(Garbage Collector、以下GC)があります。
そのおかげでオブジェクトを使い終わったあとも破棄のことは考えずにほっといて大丈夫なわけです。
GCは、いろいろとややこしい仕組みで動いているんですが、ものすごく単純に言えば「誰も参照しなくなったらそのうち破棄される」ということです。
たとえば、
|
こんな風にnullを代入して誰も参照しなくなったらnew Hoge()で生成されたオブジェクトはそのうち破棄されます。
また、
|
こんな風にスコープを抜けたとき(Func()からリターンしたとき)も誰も参照しなくなったことになるのでそのうち破棄されます。
この「そのうち」っていうところに注意してください。決してすぐに破棄されるわけじゃありません。
GCはそれなりに大変な処理なので、そんなに頻繁に動きません。
それどころか、.NET FrameworkのGCは「メモリが足りなくなってきたら動く」という感じらしいです。
まぁ、これ自体は何も問題ありません(どっちにしろそのうち破棄されるんだから)。
けど、オブジェクトがメモリ以外のリソースを持っているときは問題になることがあります。
たとえば、
|
こんな場合です(えらく意図的ですが)。
Func()メソッドからリターンした時点でfsやsrを参照しているものは無くなりますから、そのうちGCによって破棄されます。
が、破棄されるまでは"test.txt"はオープンされたままになってしまいます。
FileShare.Noneを指定してオープンしてますから、クローズされるまでは誰もオープンできなくなります(ロックがかかる。FileShare.Noneはそういう指定です)。
ということは、次にいつ"test.txt"がオープンできるかは「GCまかせ」ってことです。
それじゃあ困ります。
StreamReaderクラスには、ちゃんとClose()メソッドが用意されています。
「使い終わったらちゃんとClose()を呼びましょう」
まぁ、それはそれで正解です。けど、.NET Frameworkにはもっと統一的な手法が用意されています。
IDisposableインターフェースがそれです。
IDisposableインターフェースは、上記のような「使い終わったらリソースを開放する必要がある」ということを表現するためにあります。
で、このIDisposableインターフェースには、「使い終わったリソースを開放する」ためのDispose()というメソッドが1つあるだけです。
逆に言うと、「使い終わったらリソースを開放する必要がある」ようなクラスはIDisposableインターフェースを実装しています
(普通は。もちろん、そのようなクラスにIDisposableを実装するのは、そのクラスを作る人の責任です。
ひょっとしたらIDisposableを実装してないようなものもあるかもしれません。
標準のクラスライブラリはきちんとIDisposableを実装してますが)。
すなわち、IDisposableを実装しているクラスは「リソースを抱え込んでいるから、使い終わったらDispose()を呼び出してね」
という意思表示をしていると思ったらいいんです。
ようするに、あるクラスがIDisposableを実装していたら、使い終わったあとにとにかくDispose()を呼んでやればいいんです。 「そのクラスはメモリ以外のリソースを使っているのかな?」とかって考える必要はありません。 それに「Close()だっけ?CloseHandle()だっけ?それともCloseFile()だっけ?」とかって悩む必要もありません。 とにかくDispose()です。
ただ、「IDisposableを実装している」と書きましたが、StreamReaderのように直接には実装していない場合もあります。 StreamReaderはその親であるTextReaderがIDisposableを実装しています。 この場合も「IDisposableを実装している」ことになるので注意してください。 それに、クラスリファレンスにはDispose()メソッドが載っていないようです (protectedなDispose(bool disposing)というメソッドは載っていますが、引数なしのpublicなDispose()メソッドは載っていません。 けど、親クラスがIDisposableを実装しているんだから無いはずありません)。
下記のコードを見てください。 ちゃんと使い終わったらDispose()を呼び出しています。 しかし、コメントのところで例外が発生したらどうなるでしょう?
|
コメントのところで例外が出るとDispose()の行に到達することなく、catchに移動してしまいます。 もちろん、メソッドが終了した時点でfs、srを参照するものは無くなりますが、GCが動いてこれらが破棄されるまでファイルはオープンされたままになってしまいます。 これを防ぐために下記のように必ずfinallyでDispose()するようにします。
|
C#には、自動的にDispose()を呼び出してくれ、しかも、例外にも対応してくれる便利な構文があります。 それがusingです。 usingを使うとこんな風に書けます。
|
このコードは下記のコードとまったく同じ意味です。 実際、これらのコードのILを見比べると100%まったく同じです。
|
ただ、逆に言うとusingは自動的にtry ... finally を作ってくれて、finallyで必ずDispose()を呼び出すようにしてくれるだけです。 なので、例外に対処するために下記のようなコードになることが多いでしょう。
|
上記のようにusingがネストしているときは、下記のように書くことができます。
|
1つ目のusingの末尾に";"も"{"も無いところに注意してください。
構文的にはなんとも気持ち悪いですが、結構便利です。
また、できあがったILは普通にネストして書いたときとまったく同じです。
そんなわけで、とにかく使えるところでは必ずusingを使うようにしましょう。
また、メンバに格納するなどusingを使えない場合は、必ず使い終わった後にDispose()するようにしましょう。