28

私は using ステートメントを見ているだけです。私はそれが何をするかを常に知っていましたが、今までそれを使用しようとしなかったので、以下のコードを思いつきました:

 using (SqlCommand cmd = 
     new SqlCommand(reportDataSource, 
         new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)))
 {
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
     cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
     cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
     cmd.Connection.Open();

     DataSet dset = new DataSet();
     new SqlDataAdapter(cmd).Fill(dset);
     this.gridDataSource.DataSource = dset.Tables[0];
 }

これは機能しているようですが、SQLサーバーのダウンなどの予期しないエラーをキャッチするために、これをtry catchブロックで囲む必要があると私が知る限り、これには何かポイントがあります。何か不足していますか?

私が現在見ることができる限り、cmdを閉じて破棄するのを止めるだけですが、try catchがまだ必要であるため、コード行が増えます。

4

16 に答える 16

56

IO 作業を行うときは、例外を予期するようにコーディングします。

SqlConnection conn = null;
SqlCommand cmd = null;

try
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;

        conn.Open(); //opens connection

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch(Exception ex)
{
    Logger.Log(ex);
    throw;
}
finally
{
    if(conn != null)
        conn.Dispose();

        if(cmd != null)
        cmd.Dispose();
}

編集:明確にするために、このような状況でログインすることが重要であると信じているため、ここではusingブロックを避けています。経験から、どんな種類の奇妙な例外がポップアップするか分からないことがわかりました。この状況でログを記録すると、デッドロックを検出したり、スキーマの変更がコード ベースのほとんど使用されていない部分やほとんどテストされていない部分に影響を与えている場所、またはその他のさまざまな問題を見つけるのに役立つ場合があります。

編集 2:この状況では、using ブロックが try/catch をラップできると主張できますが、これは完全に有効であり、機能的に同等です。これは本当に好みの問題です。独自の処理を犠牲にして余分な入れ子を避けたいですか? または、自動破棄を行うために余分なネスティングが発生しますか。私は前者の方がきれいな気がするのでそのようにしています。ただし、私が作業しているコードベースで後者を見つけた場合、私は後者を書き直していません。

編集 3: MS が using() のより明示的なバージョンを作成して、実際に何が起こっているかをより直感的にし、この場合の柔軟性を高めてくれることを本当に願っています。次の架空のコードを考えてみましょう。

SqlConnection conn = null;
SqlCommand cmd = null;

using(conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString),
          cmd = new SqlCommand(reportDataSource, conn)
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString);
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
        cmd.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch(Exception ex)
{
    Logger.Log(ex);
    throw;
}

using ステートメントは、finally で Dispose() 呼び出しを使用して try/finally を作成するだけです。開発者に、廃棄と例外処理を行う統一された方法を提供してみませんか?

于 2008-10-30T01:23:48.517 に答える
18

このコードは、接続をタイムリーに閉じるために次のようにする必要があります。コマンドだけを閉じても、接続は閉じられません。

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, con))
         {
             cmd.CommandType = CommandType.StoredProcedure;
             cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
             cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
             cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
             cmd.Connection.Open();

             DataSet dset = new DataSet();
             new SqlDataAdapter(cmd).Fill(dset);
             this.gridDataSource.DataSource = dset.Tables[0];
         }

あなたの質問に答えるために、finally ブロックで同じことを行うことができますが、これによりコードのスコープが適切に設定され、クリーンアップを忘れないようにすることができます。

于 2008-10-30T01:18:13.987 に答える
14

//ブロックを使用するusing場合は、この場合にステートメントを使用しても利点がない可能性があります。ご存じのとおり、このステートメントは、オブジェクトを破棄する/の構文糖衣です。あなたが自分のものを持っているつもりなら/とにかく、あなたは確かに自分でできる.trycatchfinallyusingtryfinallyIDisposabletryfinallyDispose

これは主にスタイルに帰着します。チームはusingステートメントに慣れてusingいるかもしれませんし、ステートメントがコードをよりきれいに見せるかもしれません。

ただし、usingステートメントが非表示にするボイラープレートがとにかくそこにある場合は、それが好みであれば、先に進んで自分で処理してください。

于 2008-10-30T04:05:52.340 に答える
6

コードが次のようになっている場合:

using (SqlCommand cmd = new SqlCommand(...))
{
  try
  {
    /* call stored procedure */
  }
  catch (SqlException ex)
  {
    /* handles the exception. does not rethrow the exception */
  }
}

次に、代わりに try.. catch.. finally を使用するようにリファクタリングします。

SqlCommand cmd = new SqlCommand(...)
try
{
  /* call stored procedure */
}
catch (SqlException ex)
{
  /* handles the exception and does not ignore it */
}
finally
{
   if (cmd!=null) cmd.Dispose();
}

このシナリオでは、例外を処理することになるため、その try..catch を追加するしかありません。finally 句を挿入して、別のネスト レベルを節約することもできます。例外を無視するだけでなく、catch ブロックで何かをしている必要があることに注意してください。

于 2008-10-30T03:11:39.720 に答える
5

Chris Ballanceが言ったことを詳しく説明すると、C#仕様(ECMA-334バージョン4)のセクション15.13には、「usingステートメントは、取得、使用、廃棄の3つの部分に変換されます。リソースの使用は、次のようなtryステートメントに暗黙的に含まれます。 finally節。このfinally節は、リソースを破棄します。nullリソースが取得された場合、Disposeの呼び出しは行われず、例外はスローされません。」

説明は2ページ近くあります-一読する価値があります。

私の経験では、SqlConnection / SqlCommandは非常に多くの方法でエラーを生成する可能性があるため、予想される動作を処理するよりも多くの例外を処理する必要があります。nullリソースの場合を自分で処理できるようにしたいので、ここでusing句が必要かどうかはわかりません。

于 2008-10-30T02:31:11.080 に答える
4

「使用」の問題の 1 つは、例外を処理しないことです。「using」の設計者が、以下の疑似コードのような構文にオプションで「catch」を追加すると、はるかに便利になります。

using (...MyDisposableObj...)
{

   ... use MyDisposableObj ...

catch (exception)

   ... handle exception ...

}

it could even have an optional "finally" clause to cleanup anything other than the "MyDisposableObj" allocated at the beginning of the "using" statement... like:

using (...MyDisposableObj...)
{

   ... use MyDisposableObj ...
   ... open a file or db connection ...

catch (exception)

   ... handle exception ...

finally

   ... close the file or db connection ...

}

MyDisposableObjそれでも、処理される b/cを破棄するためのコードを記述する必要はありませんusing...

そんなのどう?

于 2011-06-21T21:56:41.233 に答える
4

使用することは、例外をキャッチすることではありません。それは、ガベージ コレクターの視野外にあるリソースを適切に破棄することです。

于 2008-10-30T01:01:57.313 に答える
2

ここには素晴らしい答えがたくさんありますが、これはまだ言われていないと思います。

何があっても...「dispose」メソッドは「using」ブロックのオブジェクトで呼び出されます。returnステートメントを配置するか、エラーをスローすると、「Dispose」が呼び出されます。

例:

「MyDisposable」というクラスを作成しました。これはIDisposableを実装し、Console.Writeを実行するだけです。これらすべてのシナリオでも、常にコンソールに書き込みます。

using (MyDisposable blah = new MyDisposable())
{
    int.Parse("!"); // <- calls "Dispose" after the error.

    return; // <-- calls Dispose before returning.
}
于 2008-10-30T02:39:40.330 に答える
2

はい、例外をキャッチする必要があります。using ブロックの利点は、コードにスコープを追加できることです。あなたは、「このコードブロック内でいくつかのことを行い、最後に到達したら、リソースを閉じて破棄する」と言っています

完全に必要というわけではありませんが、コードを使用している他の人にあなたの意図を定義し、接続などを誤って開いたままにするのにも役立ちます.

于 2008-10-30T01:03:11.350 に答える
1

using ステートメントは、IDisposable インターフェイスを実装している限り、using ブロックのパラメーターが破棄されるコンパイラによって、実際には try/finally ブロックに変更されます。指定されたオブジェクトがスコープ外になったときに適切に破棄されることを保証することを除けば、この構造を使用することによって得られるエラー キャプチャは実際にはありません。

上記のTheSoftwareJediが述べているように、SqlConnection オブジェクトと SqlCommand オブジェクトの両方が適切に破棄されていることを確認する必要があります。両方を 1 つの using ブロックにスタックするのは少し面倒で、思ったように動作しない可能性があります。

また、try/catch ブロックをロジックとして使用することにも注意してください。私の鼻が特に嫌いなコードの匂いで、初心者や締め切りに間に合うように急いでいる私たちによってよく使用されます。

于 2008-10-30T01:25:22.693 に答える
1

参考までに、この特定の例では、ADO.net 接続と Command オブジェクトを使用しているため、using ステートメントは実際には接続を閉じない Command.Dispose と Connection.Dispose() を実行するだけであることに注意してください。次のconnection.openで再利用されるようにADO.net接続プールに解放するだけです...これは良いことであり、絶対に正しいことです。そうしないと、ゴミが出るまで接続は使用できなくなります。コレクターはそれをプールに解放します。これは、他の多数の接続要求まで行われない可能性があります。そうしないと、ガベージ コレクションを待っている未使用の接続がある場合でも、新しい接続を強制的に作成することになります。

于 2008-10-30T02:12:03.613 に答える
1

扱っているリソースに応じて、using ステートメントをいつ使用し、いつ使用しないかを決定します。ODBC 接続などのリソースが限られている場合は、T/C/F を使用して、発生した時点で意味のあるエラーをログに記録できるようにします。データベースドライバーのエラーがクライアントにバブルバックし、より高いレベルの例外ラッピングで失われる可能性があるのは、最適ではありません。

T/C/F は、リソースが希望どおりに処理されているという安心感を与えてくれます。既に述べたように、using ステートメントは例外処理を提供せず、リソースが破棄されることを保証するだけです。例外処理は、十分に活用されておらず、過小評価されている言語構造であり、多くの場合、ソリューションの成功と失敗の違いです。

于 2011-06-28T21:07:12.153 に答える
0

関数の呼び出し元が例外の処理を担当している場合、usingステートメントは、結果に関係なくリソースがクリーンアップされるようにするための優れた方法です。

これにより、レイヤー/アセンブリの境界に例外処理コードを配置でき、他の関数が乱雑になるのを防ぐことができます。

もちろん、それは実際にはコードによってスローされる例外のタイプに依存します。usingステートメントではなく、try-catch-finallyを使用する必要がある場合があります。私の習慣は、常にIDisposablesのusingステートメントから始め(またはIDisposablesを含むクラスにインターフェースを実装させる)、必要に応じてtry-catch-finallyを追加することです。

于 2008-10-30T03:45:55.703 に答える
0

例のマイナーな修正:ステートメントSqlDataAdapterでインスタンス化する必要もあります:using

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, con))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    con.Open();

    DataSet dset = new DataSet();
    using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
    {
        adapter.Fill(dset);
    }
    this.gridDataSource.DataSource = dset.Tables[0];
}
于 2010-08-12T19:21:42.773 に答える
0

まず、コード例は次のようになります。

using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, conn))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    cmd.Connection.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}

質問のコードでは、コマンドを作成する例外により、作成されたばかりの接続が破棄されません。以上で、接続が適切に配置されます。

接続とコマンドの構築(およびそれらの使用時) で例外を処理する必要がある場合は、すべてを try/catch でラップする必要があります。

try
{
    using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
    using (SqlCommand cmd = new SqlCommand(reportDataSource, conn))
    {
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
        cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
        cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
        cmd.Connection.Open();

        DataSet dset = new DataSet();
        new SqlDataAdapter(cmd).Fill(dset);
        this.gridDataSource.DataSource = dset.Tables[0];
    }
}
catch (RelevantException ex)
{
    // ...handling...
}

connただし、クリーンアップやcmd;を処理する必要はありません。それはすでにあなたのために行われています。

のない同じものと対比してusingください:

SqlConnection conn = null;
SqlCommand cmd = null;
try
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString);
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    cmd.Connection.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch (RelevantException ex)
{
    // ...handling...
}
finally
{
    if (cmd != null)
    {
        try
        {
            cmd.Dispose();
        }
        catch { }
        cmd = null;
    }
    if (conn != null)
    {
        try
        {
            conn.Dispose();
        }
        catch { }
        conn = null;
    }
}
// And note that `cmd` and `conn` are still in scope here, even though they're useless

どちらを書きたいかはわかっています。:-)

于 2015-06-27T13:08:41.410 に答える
0

したがって、基本的に、「使用」は「試行/キャッチ/最終」とまったく同じですが、エラー処理の柔軟性がはるかに高くなります。

于 2010-03-25T16:57:29.520 に答える