44
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

StreamReader.Close()を呼ばなくていい理由

Last updated at Posted at 2014-06-05

はじめに

StreamReaderは、使い終わったら Close() を呼び出してストリームを閉じる必要があります。
しかし、usingブロックを使用した場合は、明示的に Close() を呼ばなくても自動的にストリームが閉じられます。

基本
StreamReader sr = new StreamReader(...);
sr.Read();
sr.Close();
usingを使用する場合
using(StreamReader sr = new StreamReader(...))
{
    sr.Read();
}

これは何故でしょうか?
ちょっと調べてみました。

usingブロックとは

usingブロックは、IDisposableインタフェース を実装したインスタンスの Dispose() を、ブロック終端到達時に自動的に呼び出してくれる便利文法です。

StreamReaderIDisposableインターフェース を実装してるので、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 のリソースが解放されるようです。

  1. usingブロック終端で StreamReader.Dispose() が呼ばれる。
  2. StreamReader.Dispose() の内部で StreamReader.Dispose(true) が呼ばれる。
  3. StreamReader.Dispose(true) にて StreamReader のリソース解放処理が行われる。

Close()を呼ばなくてもリソースが解放されるのは、こういう仕組みだったからのようですね。

補足

さて、Close()を呼ばなくてもリソース解放処理が安全に走る理由は分かりました。
しかしそうなると、

  • Close() はなんのために存在しているの?
  • Dispose()Close()はどういう使い分けをすればいいの?

という疑問にぶつかります。

これについて、皆さまからコメントをいただきましたので、ここにまとめさせていただきます。

  1. .NET1.1では、外部リソースを取り扱う処理は Open()Close() を用いてリソースの取得/解放を行うのが主流だった。

  2. しかし、.NET2.0でDisposeパターンが確立される従い、Open()Close() は、コンストラクタ/Dispose() の中に埋め込まれるようになる。

  3. さらにDisposeパターンが浸透するにつれ、「リソース解放処理の主体はDispose()であり、Dispose()で処理を実装するべき」という思想に変わっていく。

  4. これにより、Close()の解放処理はDispose()に移された。

  5. 既存ソースとの互換性を確保するため、 Close() も引き続き公開されることになる。ただし、実装自体は内部で Dispose() を呼び出すだけになった2

このような歴史的経緯があるそうです。
@ngyukiさん、@kuchikiosさん、@matarilloさん、ご助言ありがとうございました。

  1. 訳文がめちゃくちゃって意味です。「エキサイト翻訳」って言葉、もう通じないかな…?(´・ω・`)

  2. StreamReader.Close()の注釈にも書いてありますね。

44
41
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?