web-dev-qa-db-ja.com

C#で例外を再スローするための適切な方法は何ですか?

私のパートナーが私のやり方とは違うやり方をしていることから生じる質問があります。

これを行う方が良いですか?

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw;
}

またはこれ:

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw ex;
}

彼らは同じことをしますか?一方が他方より優れていますか?

422

例外を再スローするには、常に次の構文を使用する必要があります。そうしないと、スタックトレースが無効になります。

throw;

「throw ex」の結果のトレースを印刷すると、実際の例外の原因ではなく、そのステートメントで終了していることがわかります。

基本的に、 "throw ex"を使用することは刑事犯罪と見なされるべきです。

742

私の好みは使用することです

try 
{
}
catch (Exception ex)
{
     ...
     throw new Exception ("Put more context here", ex)
}

これにより元のエラーは保持されますが、オブジェクトID、接続文字列などのコンテキストを増やすことができます。多くの場合、私の例外報告ツールには5つの連鎖例外が報告されていますが、それぞれ詳細に報告されています。

151
RB.

out変数を使用して例外をスローした場合(2番目の例)、StackTraceには例外をスローした元のメソッドが含まれます。

最初の例では、StackTraceは現在のメソッドを反映するように変更されます。

例:

static string ReadAFile(string fileName) {
    string result = string.Empty;
    try {
        result = File.ReadAllLines(fileName);
    } catch(Exception ex) {
        throw ex; // This will show ReadAFile in the StackTrace
        throw;    // This will show ReadAllLines in the StackTrace
    }
35
Bobby Z

1つ目は例外の元のスタックトレースを保存し、2つ目は例外を現在の場所に置き換えます。

したがって、最初のほうがBY FARの方が優れています。

22
Quibblesome

私はこれが古い質問であることを知っています、しかし私はここですべての答えに同意しなければならないので私はそれに答えるつもりです。

さて、私はあなたがどちらか間違ったことについてできるだけ多くの情報を保存するために平易なthrowをしたいか、あるいは内部例外としてそれを含むかもしれない新しい例外を投げたいかのどちらかに同意します。それを引き起こした内部の出来事についてあなたがどれだけ知りたいのかに応じて。

ただし例外があります。あるメソッドが別のメソッドを呼び出す場合がいくつかあります。内側の呼び出しで例外を引き起こす条件は、外側の呼び出しでも同じ例外と見なす必要があります。

一例として、別のコレクションを使用して実装された特殊コレクションがあります。 DistinctList<T>をラップしますが、重複する項目を拒否するのはList<T>であるとしましょう。

誰かがあなたのコレクションクラスでICollection<T>.CopyToを呼び出した場合、それは内部コレクションのCopyToへの直接的な呼び出しであるかもしれません(例えば、すべてのカスタムロジックはコレクションへの追加や設定にのみ適用されます)。これで、その呼び出しがスローする条件は、コレクションがICollection<T>.CopyToのドキュメントと一致するようにスローする条件とまったく同じです。

今、あなたはまったく実行を捕まえることができず、それを通過させることができませんでした。ただし、ここでは、ユーザーがList<T>で何かを呼び出していたときに、ユーザーがDistinctList<T>から例外を受け取ります。世界の終わりではありませんが、それらの実装の詳細を隠したいと思うかもしれません。

それとも、あなたはあなた自身のチェックをすることができます:

public CopyTo(T[] array, int arrayIndex)
{
  if(array == null)
    throw new ArgumentNullException("array");
  if(arrayIndex < 0)
    throw new ArgumentOutOfRangeException("arrayIndex", "Array Index must be zero or greater.");
  if(Count > array.Length + arrayIndex)
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.");
  _innerList.CopyTo(array, arrayIndex);
}

これは最悪のコードではありません。定型的なコードであり、他のCopyToの実装から単純にパススルーではないので、それをコピーすることができるため、自分で実装する必要がありました。それでも、_innerList.CopyTo(array, arrayIndex)で行われることになっているまったく同じチェックを不必要に繰り返すので、コードに追加されたのはバグがある可能性がある6行だけです。

チェックしてラップすることができます。

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentNullException ane)
  {
    throw new ArgumentNullException("array", ane);
  }
  catch(ArgumentOutOfRangeException aore)
  {
    throw new ArgumentOutOfRangeException("Array Index must be zero or greater.", aore);
  }
  catch(ArgumentException ae)
  {
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.", ae);
  }
}

バグが発生する可能性がある新しいコードが追加されたという点で、これはさらに悪いことです。そして、私たちは内部の例外から物事を得ません。このメソッドにnull配列を渡してArgumentNullExceptionを受け取った場合、内部例外を調べて_innerList.CopyToの呼び出しにnull配列が渡されてArgumentNullExceptionがスローされたことを知ることでは何も習得できません。

ここでは、私たちが望むすべてのことを実行できます。

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
}

ユーザーが誤った引数でそれを呼び出した場合にスローする必要があると予想される例外はすべて、その再スローによって正しくスローされます。ここで使用されているロジックにバグがある場合、それは2行のうちの1行です - これがこのアプローチがうまくいくケースであると判断するのに間違っていたか、例外タイプとしてArgumentExceptionを探すのに間違っていました。それはcatchブロックが持つことができる唯一の2つのバグです。

今すぐ私は、ほとんどの場合、プレーンなthrow;が欲しい、または問題のメソッドの観点からより直接的に問題に一致するようにあなた自身の例外を構築したいのどちらかであることに同意します。上記のような再投げたほうが意味がある場合がありますが、他にもたくさんのケースがあります。例えば。 FileStreamXmlTextReaderを使用して実装されたATOMファイルリーダーがファイルエラーまたは無効なXMLを受け取った場合、それらのクラスから受け取ったものとまったく同じ例外をスローしたいと思うでしょう。 AtomFileReaderまたはFileNotFoundExceptionをスローしているのはXmlExceptionであるため、これらは同様に再スローの候補になる可能性があります。

編集する

2つを組み合わせることもできます。

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
  catch(Exception ex)
  {
    //we weren't expecting this, there must be a bug in our code that put
    //us into an invalid state, and subsequently let this exception happen.
    LogException(ex);
    throw;
  }
}
17
Jon Hanna

あなたはいつも "throw;"を使うべきです。 .NETで例外を再スローする

これを参照してください http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

基本的にMSIL(CIL)は2つの命令 - "throw"と "rethrow"とC#の "throw ex;"を持っています。 MSILの "throw"とC#の "throw;"にコンパイルされます。 - MSILへの「再投げ」!基本的に、 "throw ex"がスタックトレースを上書きする理由がわかります。

8
Vinod T. Patil

最初のほうがいいです。 2番目のコードをデバッグしてコールスタックを調べようとすると、元の例外がどこから発生したのかわかりません。あなたが本当に再スローする必要があるなら、コールスタックをそのままにしておくためのトリックがあります(検索を試してください、それは前に答えられました)。

4
Mendelt

場合によります。デバッグビルドでは、できるだけ少ない労力で元のスタックトレースを確認したいと思います。その場合は、 "throw;"請求書に合います。ただし、リリースビルドでは、(a)オリジナルのスタックトレースを含めてエラーをログに記録します。それが完了したら、(b)エラー処理を見直してユーザーにわかりやすくします。ここで「例外を投げる」は意味があります。エラーを再スローしても元のスタックトレースは破棄されますが、開発者以外の人はスタックトレース情報を見ても何も得られないので、エラーを再スローしても問題ありません。

        void TrySuspectMethod()
        {
            try
            {
                SuspectMethod();
            }
#if DEBUG
            catch
            {
                //Don't log error, let developer see 
                //original stack trace easily
                throw;
#else
            catch (Exception ex)
            {
                //Log error for developers and then 
                //throw a error with a user-oriented message
                throw new Exception(String.Format
                    ("Dear user, sorry but: {0}", ex.Message));
#endif
            }
        }

「Throw:」と「Throw ex;」のピッチングを組み合わせた、質問の表現方法。ちょっとしたニシンになります。本当の選択は「投げる」の間です。 「例外を投げる」、「元を投げる」、 "例外を投げる"のありそうもない特別なケースです。

3
Perry Tribolet

捕捉されたのと同じメソッドで例外がスローされた場合、スタックトレースは保持されないため、価値があります。

void testExceptionHandling()
{
    try
    {
        throw new ArithmeticException("illegal expression");
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        System.Diagnostics.Debug.WriteLine("finally called.");
    }
}
3
James