none
SqlConnectionとSqlDataReaderをusingで囲った場合Closeは必要? RRS feed

  • 質問

  • SQL Serverからデータを取得するプログラムを書いています。

    usingを使った場合に、

    質問1.SqlConnectionとSqlDataReaderを明示的にCloseしないといかないのか、

    また

    質問2.それが必要な場合はfinally内に記載したほうがいいのか、

    質問3.SqlCommandもusingで囲んだ方がいいのか。

    皆さんのご意見をお聞かせください。

    string data;
    
    string connectionString = "(サーバーへの接続情報)";
    string selectCommandText = "(実行するコマンド)";
    
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        try
        {
            using (SqlCommand selectCommand = new SqlCommand(selectCommandText, connection))
            {
                connection.Open();
                using (SqlDataReader reader = selectCommand.ExecuteReader())
                {
                    try
                    {
                        reader.Read();
                        data = reader["(取得する列名)"].ToString();
                    }
                    finally
                    {
                        reader.Close();
                    }
                }
            }
        }
        finally
        {
            connection.Close();
        }
    }
    

    そのほか、スマートな書き方があれば教えてください。

    2013年4月12日 7:25

回答

  • ドキュメントを読みましょう。

    まずSqlConnection.Close()には

    Close と Dispose は、機能的に同じです。

    とはっきり書かれています。SqlDataReader.Close()にはそのようなことは書かれていませんが、Dispose(Boolean)をクリックすると飛ばされるDbDataReader.Dispose()

    このメソッドは Close を呼び出します。

    と書かれています。

    2013年4月12日 8:26
  • この件については、.NET Framework 登場時に MSDN Forum の前身とも言える GotDotNet の掲示板でかなり長い間議論が展開されました。

    が、個人的には「そのクラスが IDisposable を実装している=using で括ることでアンマネージリソースのクリーンナップを期待できる」からといって、「そのクラスが実装している Close() などの『呼び出さなくてもよいかどうか』の根拠として万全ではない」というのが最終的な認識です。

    ほとんどの場合、IDisposable を実装しているクラスでは、using で括ることによって Close() メソッドの呼び出しなどを安全に省略することが期待できるし、実際にそいういう実装になっている場合がかなり多いはずですが、「IDisposable 実装すること」は、IDisposable の規定の範囲外である Close() などのメソッドがどういう実装であるか」とはまったく別な話であるため、「各クラスが独自に実装する Close() メソッドがどういう振る舞いをするのか」は「各クラスのドキュメントなどを参照する」のが正解、ということになります。

    2013年4月12日 9:35
    モデレータ
  • >SqlCommandはブロックを通過した時点でGCが開放する対象になるのでいいのかなと思いました。

    GC が実行されるのは「スコープを抜けた時」ではありません。

    また、IDisposable インターフェースを実装することは、そのクラスが「GC 対象外であることを示す」マークではなく、クラスインスタンスがカプセルしているであろうアンマネージリソースを速やかに、そして最悪のシナリオでも安全にアンマネージリソースを解放するための仕組みです。

    そして、SqlConnection だって、他のクラス同様、参照が失われた時点で GC 対象となります。

    で、「GC される時」に SuppressFinalize() による「Finalizer 呼び出し不要マーク」 が付与されていなければ、ガベージコレクタによって Finalizer 呼び出しが行われて、その一環として IDisposable.Dispose() 呼び出しが行われます。

    ただし、「それ=ガベージコレクタによる参照を失ったインスタンスの回収が *いつ起きるのか*」が不定であるため、典型的な処理フローでは明示的な IDisposable.Dispose() 呼び出し、あるいは using の使用が推奨されるのです。




    2013年4月14日 1:39
    モデレータ

すべての返信

  • ドキュメントを読みましょう。

    まずSqlConnection.Close()には

    Close と Dispose は、機能的に同じです。

    とはっきり書かれています。SqlDataReader.Close()にはそのようなことは書かれていませんが、Dispose(Boolean)をクリックすると飛ばされるDbDataReader.Dispose()

    このメソッドは Close を呼び出します。

    と書かれています。

    2013年4月12日 8:26
  • この件については、.NET Framework 登場時に MSDN Forum の前身とも言える GotDotNet の掲示板でかなり長い間議論が展開されました。

    が、個人的には「そのクラスが IDisposable を実装している=using で括ることでアンマネージリソースのクリーンナップを期待できる」からといって、「そのクラスが実装している Close() などの『呼び出さなくてもよいかどうか』の根拠として万全ではない」というのが最終的な認識です。

    ほとんどの場合、IDisposable を実装しているクラスでは、using で括ることによって Close() メソッドの呼び出しなどを安全に省略することが期待できるし、実際にそいういう実装になっている場合がかなり多いはずですが、「IDisposable 実装すること」は、IDisposable の規定の範囲外である Close() などのメソッドがどういう実装であるか」とはまったく別な話であるため、「各クラスが独自に実装する Close() メソッドがどういう振る舞いをするのか」は「各クラスのドキュメントなどを参照する」のが正解、ということになります。

    2013年4月12日 9:35
    モデレータ
  • 佐祐理さん、ご回答ありがとうございました。

    なるほど。

    usingを抜ける時点でDisposeが呼び出されるので、

    SqlConnectionとSqlDataReaderはusingで十分ということですね。

    SqlConnectionのドキュメントの記述は読んだのですが、SqlDataReaderの挙動がわかりませんでした。

    おかげですっきりしました。ありがとうございました。

    2013年4月12日 9:45
  • 渋木宏明さん、ご回答ありがとうございました。

    詳しく記載いただきありがとうございました。

    usingはあくまで、Disposeを保証するだけで、Closeが実行されるかは各クラスによって挙動が異なるということですね。

    とても勉強になりました。ありがとうございました。

    2013年4月12日 9:48
  • >Closeが実行されるかは各クラスによって挙動が異なるということですね。

    もう少し厳密にいうと、using が IDisposable.Dispose() を呼び出すことは保証されますが、クラス固有の実装である Close() が IDisposable.Dispose() 呼び出しと等価であるかどうか、また IDisposable.Dispose() 呼び出しが Close() 呼び出しと等価であるかどうかは各クラスの設計次第である、ということです。

    using で括った=IDipsosable.Dispose() 呼び出しを行ったら 明示的な Close() 呼び出しが不要であることを期待する人は多いと思うので、そのようなクラス設計を心がけるべきだと思いますが、「期待すること」と「実際どうであるか」は別問題なので、所見のクラスについては仕様を確認するのが吉と思います。

    2013年4月13日 1:57
    モデレータ
  • ご回答ありがとうございました。

    すごくわかりやすかったです。ありがとうございました。

    2013年4月13日 13:34
  • あ、すみません。もう1つの質問を忘れてました。

    > 質問3.SqlCommandもusingで囲んだ方がいいのか。

    データベース接続はできるだけ速やかに、かつ確実にClose()しなきゃいけないので、SqlConnectionとSqlDataReaderはusingで囲んだ方がいいと思うのですが、

    SqlCommandについてはわざわざusingで囲まなくてもいいのかな?と思うのですが、それで大丈夫でしょうか。

    2013年4月13日 13:41
  • SqlCommandについてはわざわざusingで囲まなくてもいいのかな?と思うのですが、それで大丈夫でしょうか。

    その判断理由を教えてください。

    渋木さんも私もドキュメントを読みましょうと答えているわけですが。

    2013年4月13日 14:30
  • SqlCommandはブロックを通過した時点でGCが開放する対象になるのでいいのかなと思いました。
    2013年4月13日 17:06
  • >SqlCommandはブロックを通過した時点でGCが開放する対象になるのでいいのかなと思いました。

    GC が実行されるのは「スコープを抜けた時」ではありません。

    また、IDisposable インターフェースを実装することは、そのクラスが「GC 対象外であることを示す」マークではなく、クラスインスタンスがカプセルしているであろうアンマネージリソースを速やかに、そして最悪のシナリオでも安全にアンマネージリソースを解放するための仕組みです。

    そして、SqlConnection だって、他のクラス同様、参照が失われた時点で GC 対象となります。

    で、「GC される時」に SuppressFinalize() による「Finalizer 呼び出し不要マーク」 が付与されていなければ、ガベージコレクタによって Finalizer 呼び出しが行われて、その一環として IDisposable.Dispose() 呼び出しが行われます。

    ただし、「それ=ガベージコレクタによる参照を失ったインスタンスの回収が *いつ起きるのか*」が不定であるため、典型的な処理フローでは明示的な IDisposable.Dispose() 呼び出し、あるいは using の使用が推奨されるのです。




    2013年4月14日 1:39
    モデレータ
  • なるほどそう考えるのは自由ですが、回答内容を無視して質問を続けるのは、回答者を馬鹿にし過ぎていると私はそう感じました。
    2013年4月14日 1:39
  • Dispose メソッドを呼び出す目的は、SqlConnection クラスと SqlCommand クラスでは、少々異なるのではないでしょうか?

    SqlConnection クラスで Dispose(または Close)メソッドを呼び出す目的は、有限なリソース(コネクション)のリーク防止が主と理解しています。

    .NETの例外処理 Part.2
    http://blogs.msdn.com/b/nakama/archive/2009/01/02/net-part-2.aspx

    "GC の仕組みで防がれるリークはメモリリークである、という点です。実は、アプリケーションにおけるリークには大別してメモリリークとリソースリークがあり、リソースリークは GC のみで防止することができません。"


    一方、SqlCommand クラスの Dispose メソッドを呼び出す目的は、以下のページで Microsoft (MSFT) の方が述べてられている通り、GC.SuppressFinalize メソッドを呼び出すことにより、GC が SqlCommand オブジェクトに対して冗長なファイナライザーを呼び出すことを防ぐためだと思います。

    Should I call Dispose on a SQLCommand object?
    http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataproviders/thread/916a1734-3e19-43a1-95c7-e3a2cd18369d/

    "To prevent a finalizer from running, most well written Dispose implementations call a special method called GC.SuppressFinalize, which indicates to the GC that its finalizer shouldn't be run when it falls out of scope (as the Dispose method did the clean up). The component class (which remember the SqlCommand indirectly inherits from), implements such a Dispose method. Therefore to prevent the finalizer from running (and even though it does nothing in the case of SqlCommand), you should always call Dispose."

    GC.SuppressFinalize メソッド
    http://msdn.microsoft.com/ja-jp/library/vstudio/system.gc.suppressfinalize.aspx

    2013年4月14日 5:58
  • SqlCommand クラスの Dispose メソッドを呼び出す目的は、以下のページで Microsoft (MSFT) の方が述べてられている通り、GC.SuppressFinalize メソッドを呼び出すことにより、GC が SqlCommand オブジェクトに対して冗長なファイナライザーを呼び出すことを防ぐためだと思います。

    SqlCommand内部に精通していれば、SqlCommandコンストラクタ内でGC.SuppressFinalize()は既に呼び出されているので、このような目的で呼び出す必要はないことがわかります。
    ということは「SqlCommandはブロックを通過した時点でGCが開放する対象にな」らないわけです。
    しかしSqlCommandで解放すべきネイティブリソースはないため特に問題にならないわけですが。

    しかしこれらはあくまでundocumentedな話であり、表向きはドキュメントに記載されている範囲で考慮し、他人に説明すべきです。

    そして表向きには特段、注などありませんから、IDisposable.Dispose()を呼ぶべきという話になります。
    # 今後SqlCommandがネイティブリソースを保持する可能性もありますから。

    2013年4月15日 2:43
  • > そして表向きには特段、注などありませんから、IDisposable.Dispose()を呼ぶ
    > べきという話になります。

    Dispose を呼ぶことに異論を唱えてはいませんよ。


    渋木さんのレスで、

    > それ=ガベージコレクタによる参照を失ったインスタンスの回収が *いつ起きる
    > のか*」が不定であるため

    というところが、SqlCommand クラスの Dispose を呼ぶ理由としては自分的に引っかかったので、"it does nothing in the case of SqlCommand" だが、"to prevent the finalizer from running" のために、"you should always call Dispose." という記事を紹介しました。

    公式ドキュメントではないと言うかもしれませんが、Microsoft の看板を背負っての発言らしいということで、それなりに説得力はあると自分は思っています。

    2013年4月15日 5:23
  • 渋木宏明さん、ご回答ありがとうございました。

    DisposeとGCの挙動について勘違いしている部分がありました。ご指摘いただきありがとうございました。

    なるべくusingや明示的に呼び出すようにしたほうがよいですね。

    すごくわかりやすかったです。ありがとうございました。

    2013年4月15日 5:33
  • > そう考えるのは自由ですが

    回答になってません。

    回答内容を無視して

    無視していません。きちんとお返事しています。

    すみません。僕の理解力不足でドキュメントを読んでもわからない点を質問させていただいております。

    2013年4月15日 5:35
  • SurferOnWwwさん、ご回答ありがとうございました。

    なるほど。Disposeの呼び出す目的が違うのですね。とても勉強になります。ありがとうございました。

    2013年4月15日 5:42