アンマネージリソースを解放について(IDisposable,Dispose,Finalize) への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/5/7 14:25:34
追加で、Pen や Brush のような存在にも注意してください。

これらは内部で GDI+ API のハンドルを保持するため、使用後には Dispose を
必要とするのですが、Pens.Red や Brushes.Blue などについては、勝手に
Dispose するわけにはいきません。

何故なら、これらは自分で生成したインスタンスではなく、
.NET Framework 側で内部的に生成されるインスタンスであるためです。
Pens.Red.Dispose してしまうと、アプリケーションドメインを再起動するまで
Pens.Red を利用することができなくなってしまいます。


これと同様なものとしては、Graphics オブジェクトがあります。

Control.CreateGraphics や Graphics.FromImage などで取得した
インスタンスの場合、Dispose を明示的に呼び出す義務が発生しますが、
Paint イベント(あるいは OnPaint メソッド)の引数から得た e.Graphics は
Dispose してはいけません。



なお判断がつかないものに関しては、内部実装をソースから読み取るという手があります。
(ただし残念ながら、すべてのソースが公開されているわけではありません)
http://www.jankh.net/menu-memo-pg/menu-memo-dotnet/28-content-dotnet-source.html
投稿者 mn  (社会人) 投稿日時 2012/5/7 10:31:11
>一つの目安としては、「有限のリソースあるいは排他利用的なリソースを管理しているかどうか」でしょうか。
参考になります。SqlConnectionはそうですね。

>SqlCommand は、確かに Dispose を実装しています。しかし実際には、この中では
>ほとんど何も行われていません。プリペアドクエリー等で使われる内部キャッシュをクリアする程度の
>処理であり、早期解放が必要な資源を後始末しているわけでは無いからです。
見落としかもしれませんが、MSDNライブラリでは見つけられませんでした。

>結局のところ、Using が必要か否かというのはそれぞれのクラスによって異なりますので、
>そのクラスの説明書(ヘルプ、マニュアル、ソースコード等)などを見て判断する必要があります。
そのようですね。
経験が浅い私にはなんとも難しいですね。
ありがとうございました。
また他に参考になるやり取りがあったので紹介します。
http://social.msdn.microsoft.com/Forums/ja-JP/vsgeneralja/thread/9e145eb0-e85a-408b-8849-4ae4a705a728

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/5/1 21:46:30
一つの目安としては、「有限のリソースあるいは排他利用的なリソースを管理しているかどうか」でしょうか。


> SqlConnectionは明示的にUsingステートメントを使用していますが、
SqlConnection.Close メソッドの説明を読むと、SqlConnection は明示的な切断(Close)を
必要とするクラスである旨の解説があります。接続資源は有限なリソースです。
※ちなみに SqlConnection.Dispose メソッドは、内部で Close を呼び出しています。


> SqlCommandはFinalizeまかせになっています。
SqlCommand は、確かに Dispose を実装しています。しかし実際には、この中では
ほとんど何も行われていません。プリペアドクエリー等で使われる内部キャッシュをクリアする程度の
処理であり、早期解放が必要な資源を後始末しているわけでは無いからです。


> 検索してみましたが、IDisposableインターフェイスを実装しているなら、Disposeよんでおけという感じでした。
個人的には、一般論としては NG と思っているのですが、限定的には「アリ」だと思います。

仮に、IDisposable.Dispose で、何も処理されていないクラスがあった場合、
呼ぼうが呼ぶまいが結果は変わりません。また、解放処理が記述されているクラスなら、
不要になった資源の早期解放は望ましい行為なので、やはり Dispose を呼ぶべきでしょう。
そういう意味においては、Yes と言えなくもありません。

ですが、どんなクラスでも必ず呼ぶべきかというと、必ずしもそうではありません。
当然ながら、「まだ使用されている資源を Dispose するのは NG」だからです。

たとえば System.IO.StreamWriter/StreamReader は、Dispose メソッド内に
「基となる Stream (FileStream とか MemoryStream とか)を閉じる」という実装が
含まれているため、無条件に Close や Dispose を呼ぶわけにもいきません。

'これは OK 
Using stm As New MemoryStream()
    Dim writer As New BinaryWriter(stm)
    writer.Write(UInt16.MaxValue)
    writer.Write(UInt16.MaxValue)

    stm.Flush()
    stm.Position = 0

    Dim reader As New BinaryReader(stm)
    Dim value As Integer = reader.ReadInt32()

    MsgBox(value)
End Using



'これは「ObjectDisposedException」を引き起こす 
Using stm As New MemoryStream()
    Using writer As New BinaryWriter(stm)
        writer.Write(UInt16.MaxValue)
        writer.Write(UInt16.MaxValue)
    End Using
    stm.Flush()
    stm.Position = 0
    Using reader As New BinaryReader(stm)
        Dim value As Integer = reader.ReadInt32()
        MsgBox(value)
    End Using
End Using




> なぜUsingステートメントを使用、不使用と使い分けしているのかがわかりません。

Dispose が省略されるクラスの多くは、System.ComponentModel.Component を
継承したクラスだと思います。その理由は、
・Component 自体は IDisposable であるため、継承クラスも自動的に Using 可能なクラスとなる。
・しかし Component クラスの Dispose 自体には、即時解放を要する処理が
 記述されていない。(Component.Site.Container に対する処理が行われる程度)
・継承先クラスの Dispose では、即時解放を求める処理が追加される可能性があるが、
 少なくとも SqlCommand クラスでは、Dispose 内にてそうした処理が行われていない。
という理由によるものです。

# これとは別に、「本当は必要だが、コードの本質が分かりにくくなることを嫌い、
# サンプルコードでは終了処理などの記述をあえて省略している」というケースも
# あろうけれど、それは Dispose の話に限ったことでは無いので今回は割愛。


ちなみに Dispose が必要な Component 継承クラスとしては、身近なものでは
System.Windows.Forms.Timer クラスがあります。これの処理内容としては、
Timer が稼働中だった場合にタイマーを停止させるというものです。
(タイマー停止と共に、Timer が使っていた OS の資源は解放される)

ちなみに、デザイン時にフォームに Timer を貼っておいた場合、
フォーム終了時に Dispose される仕組みになっています。


結局のところ、Using が必要か否かというのはそれぞれのクラスによって異なりますので、
そのクラスの説明書(ヘルプ、マニュアル、ソースコード等)などを見て判断する必要があります。

たとえば Form クラスなどのように
 ・Show で呼び出した場合は、呼び出し側が Dispose するべきではない
 ・ShowDialog で呼び出した場合は、呼び出し側で Dispose が必要
というケースもありますしね。
投稿者 mn  (社会人) 投稿日時 2012/5/1 17:40:48
こんにちは。
次のURLの中の使用例の部分についてご意見下さい。
http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlcommand%28v=vs.80%29.aspx

SqlConnectionは明示的にUsingステートメントを使用していますが、SqlCommandはFinalizeまかせになっています。SqlCommand、SqlDataReaderもUsingステートメントを使用してもいいと思いますし、SqlConnectionからUsingステートメントをはずしてFinalizeまかせにしてもいいと思います。
なぜUsingステートメントを使用、不使用と使い分けしているのかがわかりません。


検索してみましたが、IDisposableインターフェイスを実装しているなら、Disposeよんでおけという感じでした。