web-dev-qa-db-ja.com

C#ステートメントキャッチエラーの使用

私はただ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];
 }

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

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

28
PeteT

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

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ブロックで同じことを行うことができますが、これはコードを適切にスコープし、クリーンアップすることを忘れないようにします。

18
TheSoftwareJedi

IO作業を行うとき、例外をexpectにコード化します)。

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を作成するだけです。開発者に、破棄と例外処理を行うための統一された方法を提供しないのはなぜですか?

57
Jason Jackson

とにかくusing/try/catchブロックを使用する場合は、この場合にfinallyステートメントを使用しても利点がない場合があります。ご存知のように、usingステートメントは、tryオブジェクトを破棄するfinally/IDisposableの構文糖衣です。とにかく独自のtry/finallyを作成する場合は、Disposeを自分で作成することができます。

これは主にスタイルに要約されます。チームはusingステートメントに慣れているかもしれませんし、usingステートメントはコードをきれいに見えるかもしれません。

ただし、ボイラープレートのusingステートメントが非表示になっている場合でも、とにかく、それが好みの場合は自分で処理してください。

14
Michael Burr

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

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

次に、try .. catch ..最終的に代わりにそれをリファクタリングします。

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ブロックで何かをしている必要があることに注意してください。

6
jop

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

説明は2ページに近い-読む価値があります。

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

5
Kevin Haines

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

4
Amy B

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

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...によって処理されます。

どうですか?

4
Rick

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

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

例:

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

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

    return; // <-- calls Dispose before returning.
}
2
Timothy Khouri

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

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

2
JamesSugrue

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

1
Charles Bretana

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

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

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

1
Chris Ballance

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

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

1
SteveK

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

0
Craig

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

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];
}
0
John Saunders

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

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

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

0
Andrew Kennan

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

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];
}

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

接続とコマンドのconstructionで例外を処理する必要がある場合(およびそれらを使用する場合)、はい、すべてを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

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

0
T.J. Crowder