例外をキャッチして再スローするときに考慮すべきベストプラクティスは何ですか?Exception
オブジェクトInnerException
とスタックトレースが保持されていることを確認したいと思います。次のコードブロックの処理方法に違いはありますか?
try
{
//some code
}
catch (Exception ex)
{
throw ex;
}
対:
try
{
//some code
}
catch
{
throw;
}
例外をキャッチして再スローするときに考慮すべきベストプラクティスは何ですか?Exception
オブジェクトInnerException
とスタックトレースが保持されていることを確認したいと思います。次のコードブロックの処理方法に違いはありますか?
try
{
//some code
}
catch (Exception ex)
{
throw ex;
}
対:
try
{
//some code
}
catch
{
throw;
}
スタックトレースを保持する方法は、これを使用することです。throw;
これも有効です。
try {
// something that bombs here
} catch (Exception ex)
{
throw;
}
throw ex;
throw ex;
基本的には、その時点から例外をスローするようなものであるため、スタックトレースは、ステートメントを発行している場所にのみ移動します。
例外によって例外を渡すことができると仮定すると、マイクも正しいです(これが推奨されます)。
Karl Seguinは、プログラミングの電子書籍の基礎においても例外処理について素晴らしい記事を書いています。これは素晴らしい読み物です。
編集:プログラミングの基礎pdfへの作業リンク。テキストで「例外」を検索するだけです。
最初の例外で新しい例外をスローすると、最初のスタックトレースも保持されます。
try{
}
catch(Exception ex){
throw new MoreDescriptiveException("here is what was happening", ex);
}
throw
実際には、ステートメントがStackTrace情報を保持しない状況がいくつかあります。たとえば、次のコードでは次のようになります。
try
{
int i = 0;
int j = 12 / i; // Line 47
int k = j + 1;
}
catch
{
// do something
// ...
throw; // Line 54
}
StackTraceは、行47で発生したものの、行54で例外が発生したことを示します。
Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
at Program.WithThrowIncomplete() in Program.cs:line 54
at Program.Main(String[] args) in Program.cs:line 106
上記のような状況では、元のStackTraceを保存するための2つのオプションがあります。
Exception.InternalPreserveStackTraceの呼び出し
これはプライベートメソッドであるため、リフレクションを使用して呼び出す必要があります。
private static void PreserveStackTrace(Exception exception)
{
MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
}
StackTrace情報を保持するためにプライベートメソッドに依存するという欠点があります。.NETFrameworkの将来のバージョンで変更される可能性があります。上記のコード例と以下の提案されたソリューションは、FabriceMARGUERIEウェブログから抽出されました。
Exception.SetObjectDataの呼び出し
以下の手法は、In C#への回答としてAnton Tykhyyによって提案されました。スタックトレースの質問を失うことなく、InnerExceptionを再スローするにはどうすればよいですか。
static void PreserveStackTrace (Exception e)
{
var ctx = new StreamingContext (StreamingContextStates.CrossAppDomain) ;
var mgr = new ObjectManager (null, ctx) ;
var si = new SerializationInfo (e.GetType (), new FormatterConverter ()) ;
e.GetObjectData (si, ctx) ;
mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData
mgr.DoFixups () ; // ObjectManager calls SetObjectData
// voila, e is unmodified save for _remoteStackTraceString
}
ただし、パブリックメソッドのみに依存するという利点がありますが、次の例外コンストラクターにも依存します(サードパーティによって開発された一部の例外は実装されていません)。
protected Exception(
SerializationInfo info,
StreamingContext context
)
私の状況では、最初のアプローチを選択する必要がありました。これは、使用していたサードパーティのライブラリによって発生した例外がこのコンストラクターを実装していなかったためです。
するとthrow ex
、基本的に新しい例外がスローされ、元のスタックトレース情報を見逃してしまいます。 throw
推奨される方法です。
経験則では、基本的なException
オブジェクトをキャッチしてスローすることは避けてください。これにより、例外について少し賢くなります。言い換えるSqlException
と、処理コードが。で何か問題を起こさないように、aを明示的にキャッチする必要がありますNullReferenceException
。
現実の世界では、基本例外をキャッチしてログに記録することも良い習慣ですが、それが持つ可能性のあるものを取得するためにすべてを歩くことを忘れないでくださいInnerExceptions
。
ExceptionDispatchInfo.Capture( ex ).Throw()
と a plainの違いは誰も説明していないthrow
ので、ここで説明します。しかし、一部の人々は の問題に気付きましたthrow
。
キャッチされた例外を再スローする完全な方法は、使用することExceptionDispatchInfo.Capture( ex ).Throw()
です (.Net 4.5 からのみ利用可能)。
以下は、これをテストするために必要なケースです。
1.
void CallingMethod()
{
//try
{
throw new Exception( "TEST" );
}
//catch
{
// throw;
}
}
2.
void CallingMethod()
{
try
{
throw new Exception( "TEST" );
}
catch( Exception ex )
{
ExceptionDispatchInfo.Capture( ex ).Throw();
throw; // So the compiler doesn't complain about methods which don't either return or throw.
}
}
3.
void CallingMethod()
{
try
{
throw new Exception( "TEST" );
}
catch
{
throw;
}
}
4.
void CallingMethod()
{
try
{
throw new Exception( "TEST" );
}
catch( Exception ex )
{
throw new Exception( "RETHROW", ex );
}
}
ケース 1 とケース 2 では、CallingMethod
メソッドのソース コードの行番号がその行の行番号であるスタック トレースが得られますthrow new Exception( "TEST" )
。
ただし、ケース 3 では、CallingMethod
メソッドのソース コードの行番号がthrow
呼び出しの行番号であるスタック トレースが得られます。これは、throw new Exception( "TEST" )
行が他の操作に囲まれている場合、例外が実際にスローされた行番号がわからないことを意味します。
ケース 4 は、元の例外の行番号が保持されるためケース 2 と似ていますが、元の例外のタイプが変更されるため、実際の再スローではありません。
常に「throw;」を使用する必要があります。.NET で例外を再スローするには、
これを参照してください、 http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
基本的に、MSIL (CIL) には「スロー」と「再スロー」の 2 つの命令があります。
基本的に、「throw ex」がスタック トレースをオーバーライドする理由がわかります。
実際に非常に重要なポイントを見逃している人もいます。「throw」と「throw ex」は同じことを行う可能性がありますが、例外が発生した行である重要な情報を提供しません。
次のコードを検討してください。
static void Main(string[] args)
{
try
{
TestMe();
}
catch (Exception ex)
{
string ss = ex.ToString();
}
}
static void TestMe()
{
try
{
//here's some code that will generate an exception - line #17
}
catch (Exception ex)
{
//throw new ApplicationException(ex.ToString());
throw ex; // line# 22
}
}
「throw」または「throw ex」のいずれかを実行すると、スタック トレースが取得されますが、行番号は #22 になるため、どの行が例外をスローしたかを正確に把握することはできません (1 つまたはいくつかしかない場合を除きます)。 try ブロック内のコード行)。例外で予期される行 #17 を取得するには、元の例外スタック トレースを使用して新しい例外をスローする必要があります。
私は間違いなく使用します:
try
{
//some code
}
catch
{
//you should totally do something here, but feel free to rethrow
//if you need to send the exception up the stack.
throw;
}
それはあなたのスタックを保存します。
次のものも使用できます。
try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}
そして、スローされた例外は、それらを処理する次のレベルにバブルアップします。
参考までに、これと「throw;」によって報告されたスタック トレースをテストしました。完全に正しいスタック トレースではありません。例:
private void foo()
{
try
{
bar(3);
bar(2);
bar(1);
bar(0);
}
catch(DivideByZeroException)
{
//log message and rethrow...
throw;
}
}
private void bar(int b)
{
int a = 1;
int c = a/b; // Generate divide by zero exception.
}
スタック トレースは例外の発生元を正しく指しています (報告された行番号) が、foo() について報告された行番号はスローの行です。ステートメントであるため、どの bar() 呼び出しが例外を引き起こしたのかを知ることはできません。