ステートメントキャッチエラーを使用したC#
-
05-07-2019 - |
質問
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がまだ必要なため、コードの行が増えます。
解決
このコードは、接続をタイムリーに閉じるために次のようにする必要があります。コマンドだけを閉じても接続は閉じられません。
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ブロックで同じことを行うことができますが、これによりコードのスコープが適切になり、忘れずにクリーンアップすることができます。
他のヒント
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を作成するだけです。開発者に、廃棄と例外処理を行う統一された方法を提供してみませんか?
try
/ catch
/ <を使用する場合、この場合は using
ステートメントを使用してもメリットがない場合がありますとにかくcode> finally ブロック。ご存じのとおり、 using
ステートメントは、 IDisposable
オブジェクトを破棄する try
/ finally
の構文糖衣です。とにかく独自の try
/ finally
を使用する場合は、 Dispose
を自分で行うことができます。
これは主にスタイルに要約されます-あなたのチームは using
ステートメントや using
ステートメントのほうがコードがよりきれいに見えるかもしれません。
しかし、とにかく using
ステートメントが隠れているボイラープレートがあれば、それがあなたの好みであれば先に進んで自分で物事を処理してください。
コードが次のようになっている場合:
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ブロックで何かを行う必要があることに注意してください。
Chris Ballanceが述べたことを詳しく述べると、C#仕様(ECMA-334バージョン4)セクション15.13には、「usingステートメントは、取得、使用、廃棄の3つの部分に変換されます」と記載されています。リソースの使用は、finally句を含むtryステートメントで暗黙的に囲まれます。このfinally節はリソースを破棄します。 nullリソースが取得された場合、Disposeの呼び出しは行われず、例外はスローされません。&quot;
説明は2ページ近くあります-読む価値があります。
私の経験では、SqlConnection / SqlCommandは非常に多くの方法でエラーを生成する可能性があるため、予想される動作を処理する以上の例外を処理する必要があります。 nullリソースのケースを自分で処理できるようにしたいので、ここでusing句が必要かどうかわかりません。
使用とは、例外をキャッチすることではありません。ガベージコレクターのビュー外にあるリソースを適切に廃棄することです。
「使用」に関する1つの問題例外を処理しないということです。 「使用」の設計者が&quot; catch&quot;を追加しますオプションで、以下の擬似コードのような構文を使用すると、はるかに便利になります。
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
...
それはどうですか?
はい、例外をキャッチする必要があります。 usingブロックの利点は、コードにスコープを追加することです。 「このコードブロック内で何らかの処理を行い、最後に到達したらリソースを閉じて破棄する」と言っています。
完全に必要というわけではありませんが、コードを使用する他の人に意図を定義するだけでなく、誤って接続などを開かないようにするのにも役立ちます。
ここには多くのすばらしい答えがありますが、まだ言われているとは思いません。
何があっても...「破棄」メソッドは、「使用中」のオブジェクトに対して呼び出されます。ブロック。 returnステートメントを挿入するか、エラーをスローすると、&quot;破棄&quot;呼び出されます。
例:
「MyDisposable」というクラスを作成しました。IDisposableを実装し、Console.Writeを実行します。これらのすべてのシナリオでも、常にコンソールに 書き込みます:
using (MyDisposable blah = new MyDisposable())
{
int.Parse("!"); // <- calls "Dispose" after the error.
return; // <-- calls Dispose before returning.
}
usingステートメントは、コンパイラによってtry / finallyブロックに実際に変更されます。コンパイラでは、usingブロックのパラメーターがIDisposableインターフェイスを実装している限り破棄されます。指定されたオブジェクトがスコープから外れたときに適切に破棄されることを保証することを除けば、この構造を使用することで得られるエラーキャプチャは実際にはありません。
上記の TheSoftwareJedi で言及されているように、SqlConnectionオブジェクトとSqlCommandオブジェクトの両方が適切に破棄されるようにする必要があります。両方を単一のusingブロックにスタックするのは少し面倒で、思ったようには動作しないかもしれません。
また、try / catchブロックをロジックとして使用することにも注意してください。私の鼻は特に嫌いなコードの匂いであり、多くの場合、初心者や私たちが締め切りに間に合わせるために大急ぎで使用します。
FYI、この特定の例では、ADO.net接続とCommandオブジェクトを使用しているため、usingステートメントはCommand.Disposeを実行するだけで、Connection.Dispose()は実際には閉じないことに注意してください接続、しかしそれを単に次のconnection.openで再利用するためにADO.net接続プールに戻します。これは良いことであり、絶対に正しいことです。そうしない場合、bcは使用できません。ガベージコレクターがプールに解放するまで。これは、他の多数の接続要求までではなく、そうしないと、ガベージコレクションを待機している未使用の接続要求があったとしても、新しい接続を作成する必要があります。
使用しているリソースに応じてusingステートメントを使用するタイミングと使用しないタイミングを決定します。 ODBC接続などのリソースが限られている場合は、T / C / Fを使用して、発生した時点で意味のあるエラーを記録できるようにします。データベースドライバーのエラーをクライアントにバブルバックさせ、より高いレベルの例外のラップで失われる可能性があるのは最適ではありません。
T / C / Fは、リソースが希望どおりに処理されているという安心感を与えます。既に述べたように、usingステートメントは例外処理を提供せず、リソースを確実に破壊します。例外処理は、ソリューションの成功と失敗の違いであることが多い、十分に活用されておらず、過小評価されている言語構造です。
関数の呼び出し元が例外を処理する責任がある場合、usingステートメントは、結果に関係なくリソースがクリーンアップされることを保証する良い方法です。
これにより、例外処理コードをレイヤー/アセンブリの境界に配置でき、他の機能が乱雑になるのを防ぐことができます。
もちろん、コードによってスローされる例外のタイプに依存します。場合によっては、usingステートメントではなくtry-catch-finallyを使用する必要があります。私の習慣は、常にIDisposablesのusingステートメントから開始し(または、IDisposablesを含むクラスにもインターフェイスを実装させる)、必要に応じてtry-catch-finallyを追加することです。
つまり、基本的には、「使用」 &quot; Try / catch / finally&quot;とまったく同じです。エラー処理の柔軟性が大幅に向上しました。
例の軽微な修正: 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];
}
最初に、コード例は次のようになります。
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
どちらを書きたいかを知っています。 :-)