はじめに
StreamReaderは、使い終わったら Close()
を呼び出してストリームを閉じる必要があります。
しかし、usingブロックを使用した場合は、明示的に Close()
を呼ばなくても自動的にストリームが閉じられます。
StreamReader sr = new StreamReader(...);
sr.Read();
sr.Close();
using(StreamReader sr = new StreamReader(...))
{
sr.Read();
}
これは何故でしょうか?
ちょっと調べてみました。
usingブロックとは
usingブロックは、IDisposableインタフェース
を実装したインスタンスの Dispose()
を、ブロック終端到達時に自動的に呼び出してくれる便利文法です。
StreamReader
は IDisposableインターフェース
を実装してるので、usingブロックで Dispose()
を呼び出してもらうことができます。
Dispose() の中で Close()?
さて、usingを用いることで StraemReader.Dispose()
が自動的に呼び出されますが、それだけでは StreamReader.Close()
は呼び出されません。
では、Dispose()
の中で Close()
が呼ばれているのでしょうか?
そこで、StreamReader.Dispose()
を MSDN で調べてみたところ。
StreamReader.Dispose()
このTextReaderオブジェクトによって使用されているすべてのリソースを解放します。 (TextReader から継承されます。)
StreamReader.Dispose()
は、親クラスである TextReader
のリソースを解放しているだけのようです。
では、StreamReader
のリソース解放はどこで行われるのでしょうか?
StreamReader.Dispose(Boolean)
さらに StreamReader
のリファレンスを調べてみると、StreamReader.Dispose(Boolean)
という、Dispose()
のオーバーロードが見つかりました。
StreamReader.Dispose(Boolean)
基になるストリームを閉じ、StreamReader によって使用されているアンマネージド リソースを解放します。任意でマネージド リソースも解放します。
どうやら Dispose(Boolean)
を呼び出せば、リソースの解放が行われるようです。
しかし、usingは Dispose()
を呼び出すだけなので、このメソッドには行きつかないはずです。
ところが、注釈の欄に以下のようなことが書いてありました。
(日本語ページは翻訳がエキサイト1だったので英文を引用)
This method is called by the public Dispose method and the Finalize() method, if it has been overridden. Dispose invokes the protected Dispose method with the disposing parameter set to true. Finalize invokes Dispose with disposing set to false.
意訳すると…
このメソッドは Dispose() および Finalize() から呼び出されます。
Dispose() は Dispose(Boolean) に true を渡して呼び出します。
Finalize() は Dispose(Boolean) に false を渡して呼び出します。
とても大切なことが、こんなところに書いてありました。
Dispose()のリファレンスにも書いておいてほしいですね。
結論
usingブロックを用いた場合、以下の流れで StreamReader
のリソースが解放されるようです。
- usingブロック終端で
StreamReader.Dispose()
が呼ばれる。 -
StreamReader.Dispose()
の内部でStreamReader.Dispose(true)
が呼ばれる。 -
StreamReader.Dispose(true)
にてStreamReader
のリソース解放処理が行われる。
Close()
を呼ばなくてもリソースが解放されるのは、こういう仕組みだったからのようですね。
補足
さて、Close()を呼ばなくてもリソース解放処理が安全に走る理由は分かりました。
しかしそうなると、
-
Close()
はなんのために存在しているの? -
Dispose()
とClose()
はどういう使い分けをすればいいの?
という疑問にぶつかります。
これについて、皆さまからコメントをいただきましたので、ここにまとめさせていただきます。
-
.NET1.1では、外部リソースを取り扱う処理は
Open()
とClose()
を用いてリソースの取得/解放を行うのが主流だった。 -
しかし、.NET2.0でDisposeパターンが確立される従い、
Open()
/Close()
は、コンストラクタ/Dispose()
の中に埋め込まれるようになる。 -
さらにDisposeパターンが浸透するにつれ、「リソース解放処理の主体は
Dispose()
であり、Dispose()
で処理を実装するべき」という思想に変わっていく。 -
これにより、
Close()
の解放処理はDispose()
に移された。 -
既存ソースとの互換性を確保するため、
Close()
も引き続き公開されることになる。ただし、実装自体は内部でDispose()
を呼び出すだけになった2。
このような歴史的経緯があるそうです。
@ngyukiさん、@kuchikiosさん、@matarilloさん、ご助言ありがとうございました。
-
訳文がめちゃくちゃって意味です。「エキサイト翻訳」って言葉、もう通じないかな…?(´・ω・`) ↩
-
StreamReader.Close()の注釈にも書いてありますね。 ↩