再スローによる不正なスタックトレース
-
26-09-2019 - |
質問
「throw;」で例外を再スローしましたが、スタックトレースが正しくありません。
static void Main(string[] args) {
try {
try {
throw new Exception("Test"); //Line 12
}
catch (Exception ex) {
throw; //Line 15
}
}
catch (Exception ex) {
System.Diagnostics.Debug.Write(ex.ToString());
}
Console.ReadKey();
}
正しいスタックトレースは次のようになります。
System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12
しかし、私は次のように感じます:
System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15
ただし、15行目は「throw;」の位置です。.NET 3.5でこれをテストしました。
解決
は、同様の方法で二回投げることはおそらく特殊なケースである - 私は、同じ方法で別の線が互いに続くスタックトレースを作成することができていませんでした。言葉として、あなたは例外が横断することをスタック・フレーム「スタックトレース」を示し、述べています。そして、メソッドの呼び出しごとに1つのだけのスタックフレームがあります!
あなたは他の方法から投げる場合、期待どおり、throw;
は、Foo()
のエントリを削除しません。
static void Main(string[] args)
{
try
{
Rethrower();
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
Console.ReadKey();
}
static void Rethrower()
{
try
{
Foo();
}
catch (Exception ex)
{
throw;
}
}
static void Foo()
{
throw new Exception("Test");
}
あなたはRethrower()
を変更し、throw;
でthrow ex;
を交換する場合、スタックトレースでFoo()
エントリが消えます。ここでも、その予想される動作です。
他のヒント
さすがに考えられるものですね。スタック トレースの変更は、指定した場合の通常のケースです。 throw ex;
, FxCop はスタックが変更されたことを通知します。作る場合には throw;
, の場合、警告は生成されませんが、トレースは変更されます。したがって、残念ながら今のところ、元を捕まえたり、インナーとして投げたりしないのが最善です。として考えるべきだと思います Windows への影響 またはそのような感じ - 編集済み.
ジェフ・リヒター この状況については彼の著書で詳しく説明されています 「C#経由のCLR」:
次のコードは、それが捕まえたものと同じ例外オブジェクトをスローし、CLRに例外の開始点をリセットします。
private void SomeMethod() { try { ... } catch (Exception e) { ... throw e; // CLR thinks this is where exception originated. // FxCop reports this as an error } }
対照的に、スローキーワードを単独で使用して例外オブジェクトを再スローする場合、CLRはスタックの出発点をリセットしません。次のコードは、それが捕まえたのと同じ例外オブジェクトを再スローし、CLRに例外の出発点をリセットしません。
private void SomeMethod() { try { ... } catch (Exception e) { ... throw; // This has no effect on where the CLR thinks the exception // originated. FxCop does NOT report this as an error } }
実際、これら2つのコードフラグメントの唯一の違いは、CLRが例外がスローされた元の場所であると考えていることです。 残念ながら、例外を投げるかrethrowすると、Windowsはスタックの開始点をリセットします。 したがって、例外がハンドルになっている場合、Windowsエラーレポートに報告されるスタックの場所は、元の例外がスローされたスタックの場所を知っているにもかかわらず、最後のスローまたは再スローの場所です。これは、フィールドで失敗したデバッグアプリケーションをはるかに困難にするため、残念です。一部の開発者は、これが非常に耐えられないことを発見したため、スタックトレースが例外が最初にスローされた場所を真に反映するようにコードを実装する別の方法を選択しました。
private void SomeMethod() { Boolean trySucceeds = false; try { ... trySucceeds = true; } finally { if (!trySucceeds) { /* catch code goes in here */ } } }
これは、CLRのWindows版ではよく知られている制限です。これは、Windowsが組み込みの例外処理(SEH)のサポートを使用しています。問題は、それがスタックフレームベース及び方法は、一つだけのスタックフレームを有している、です。あなたは簡単にこのように、別のスタックフレームを作成し、別のヘルパーメソッドに、内側のtry / catchブロックを移動することによって、問題を解決することができます。この制限の他の結果は、JITコンパイラは、try文が含まれている任意の方法をインライン化しないだろうということです。
どのように私はREALスタックトレースを保存することができますか?
あなたは新しい例外をスローし、内部例外として元の例外を含みます。
それは醜いです...長い... rigth例外がスローするあなたが選択します....
あなたはの醜いのが、権利について、他の二つの点について間違っています。親指のルールは次のとおりです。あなたはそれで何かをしようとしている場合を除き、それを飲み込むか、それを記録、ラップのように、それは、それを修正し、キャッチしません。あなたはcatch
に決定し、再度throw
場合は、必ずあなたはそれで何かをやっているにし、それ以外の場合はちょうどそれをバブルアップしてみましょう。
また、あなたは、キャッチにブレークポイントを設定することができますので、単純にキャッチを入れたくなるかもしれませんが、Visual Studioのデバッガは、その練習が不要に代わりに最初のチャンス例外または条件付きブレークポイントを使用してみてくださいするのに十分なオプションがあります。
編集/置換
実際の動作は異なりますが、微妙に異なります。はどうかと言うと なぜ 動作が異なる場合は、CLR の専門家に依頼する必要があります。
編集: AlexDさんの答え これは仕様によるものであることを示しているようです。
例外をキャッチしたのと同じメソッドで例外をスローすると状況が少し混乱するため、別のメソッドから例外をスローしましょう。
class Program
{
static void Main(string[] args)
{
try
{
Throw();
}
catch (Exception ex)
{
throw ex;
}
}
public static void Throw()
{
int a = 0;
int b = 10 / a;
}
}
もし throw;
が使用されている場合、コールスタックは次のようになります (行番号はコードに置き換えられます)。
at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified
もし throw ex;
が使用される場合、コールスタックは次のようになります。
at Main():line (throw ex;)
例外がキャッチされない場合、コールスタックは次のようになります。
at Throw():line (int b = 10 / a;)
at Main():line (Throw())
.NET 4 / VS 2010でテスト済み
重複質問はこちらここhref="https://stackoverflow.com/questions/4217616/incorrect-stacktrace-by-rethrow">
私はそれを理解として - スロー。 「再スロー」MSIL命令するにコンパイルされ、それが最後のフレームを変更していますスタックトレースます。 私はそれが元のスタックトレースを維持し、それが再スローされた行を追加して期待されるが、どうやらのが唯一のメソッドコールのあたり1つのスタックフレームすることができます。 の結論:スローを使用しての回避;再投げに新しいものにあなたの例外をラップ - それは醜いではない、それがベストプラクティスです。
あなたが使用してスタックトレースを保存することができます。
ExceptionDispatchInfo.Capture(ex);
ここでのコードサンプルがあります:
static void CallAndThrow()
{
throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
}
static void Main(string[] args)
{
try
{
try
{
try
{
CallAndThrow();
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// rollback tran, etc
dispatchException.Throw();
}
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// other rollbacks
dispatchException.Throw();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine(ex.StackTrace);
}
Console.ReadLine();
}
出力は次のようになります
Test app ex Test inner ex at TestApp.Program.CallAndThrow() in D:\Projects\TestApp\TestApp\Program.cs:line 19 at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 30 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 38 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 47
OK、.NET Framework にはバグがあるようです。例外をスローし、それを同じメソッドで再スローすると、元の行番号が失われます (メソッドの最後の行になります)。
幸いなことに、ファブリス・マルゲリーという名の賢い男が見つけてくれました。 解決策 このバグに。以下は私のバージョンです。テストできます この .NET フィドル.
private static void RethrowExceptionButPreserveStackTrace(Exception exception)
{
System.Reflection.MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
throw exception;
}
ここで、通常どおり例外をスローする代わりにキャッチします。このメソッドを呼び出すだけで、元の行番号が保持されます。
わからないかどうか、これは仕様によるものですが、私はそれは常にそのようにされていると思います。
オリジナルのスロー新しい例外が別の方法である場合には、スローのための結果は、元のメソッド名と行番号と例外が再スローされるメインで、その後の行番号を持っている必要があります。
あなたはスローEXを使用する場合は、例外が再スローであり、その後、結果はちょうどメインのラインになります。
つまり、スローexが失うのすべてのスタックトレース、スロージャムのに対し、スタックトレース履歴(下位レベルのメソッドのすなわち詳細)。あなたの例外が再スローあなたと同様の方法によって生成された場合でも、あなたはいくつかの情報を失うことができます。
のNB。あなたは非常にシンプルで小さなテストプログラムを記述する場合、フレームワークは時々物事を最適化し、その結果が「本当の」プログラムとは異なる場合があり意味インラインコードする方法を変更することができます。の
あなたは右の行番号をしたいですか?ただ、使用の のひとつの方法につきのののtry / catch。あなたがデータベーストランザクションが必要な場合は、よく、彼らはUI層にすべきではない、とあなたが持っていないので、システムでは、よく...ちょうどUI層ではなく、ロジックやデータアクセスには、これは、非常に迷惑です右の行番号が、あなたがそれらを必要としない場合は、再スローでもキャッチで例外なくはありません...
5分のサンプルコード:
メニューのファイルの - > の新規プロジェクトの、場所三つのボタン、および各1に次のコードを呼び出します:
private void button1_Click(object sender, EventArgs e)
{
try
{
Class1.testWithoutTC();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
private void button2_Click(object sender, EventArgs e)
{
try
{
Class1.testWithTC1();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
private void button3_Click(object sender, EventArgs e)
{
try
{
Class1.testWithTC2();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
}
}
さて、新しいクラスを作成します:
class Class1
{
public int a;
public static void testWithoutTC()
{
Class1 obj = null;
obj.a = 1;
}
public static void testWithTC1()
{
try
{
Class1 obj = null;
obj.a = 1;
}
catch
{
throw;
}
}
public static void testWithTC2()
{
try
{
Class1 obj = null;
obj.a = 1;
}
catch (Exception ex)
{
throw ex;
}
}
}
ファイル名を指定して実行...最初のボタンが美しいです!
私は、これはスタックトレースの行番号が決定された方法で行うには、スタックトレース変更し、より少ない場合だと思います。 Visual Studioの2010年にそれを試して、現象は、MSDNのドキュメントから期待されるものと同様である:「スローEX。」 「投げる;」、この文の時点からスタックトレースを再構築葉スタックトレースとしてそれとして、これまで例外が再スローされた場合、行番号が再スローの場所ではなく、例外が経由して来た呼び出します。
であることを除いてだから、 "スロー;" でメソッド呼び出しツリーが変更されないが、行番号が変更される可能性があります。
私はこの数回遭遇してきた、そしてそれは設計によるものであってもよいし、ちょうど完全に文書化されていません。再スローの場所は知っていることは非常に便利であるとして、彼らはこれを行っていることがその理由、そしてあなたの方法は、単純な十分に元のソースは、通常は明白とにかくだろうしている場合、私は理解することができます。
他の多くの人が言ったように、あなたが本当にしていない限り、、それは通常最高の例外をキャッチしないように、および/または、あなたはその時点でそれに対処しようとしている。
興味深い側の注:Visual Studio 2010には、さらに問題の提示と、それはコンパイル時にゼロ除算エラーを拾って、私はコードをビルドせません。
。Exception
がからスローされたことを、現金としてそれを取るので、の15行目の上でそれを再スローしているからException
をcatchedためである。そこます。
より良いハンドルの例外に、あなたは、単にtry...finally
を使用して、未処理のException
バブルをアップさせてくださいます。