42

「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 でテストしました。

4

12 に答える 12

27

同じメソッドで 2 回スローするのは、おそらく特殊なケースです。同じメソッド内の異なる行が互いに続くスタック トレースを作成できませんでした。その言葉が示すように、「スタック トレース」は、例外が通過したスタック フレームを示します。そして、メソッド呼び出しごとに 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()エントリが消えます。繰り返しますが、これは予想される動作です。

于 2010-11-18T17:29:29.553 に答える
26

想定通りと言えるものです。スタック トレースを変更するのは通常、 を指定した場合ですthrow ex;。FxCop は、スタックが変更されたことを通知します。を作成した場合throw;、警告は生成されませんが、それでもトレースは変更されます。なので残念ながら今のところexはキャッチしないかインナーとして投げないのが一番です。私はそれがWindowsの影響またはそのようなものと見なされるべきだと思います-編集. Jeff Richterは、この状況を「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
  }
}

対照的に、throw キーワードを単独で使用して例外オブジェクトを再スローした場合、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 が認識していることです。 残念ながら、例外をスローまたは再スローすると、Windows はスタックの開始点をリセットします。そのため、例外が処理されなくなった場合、CLR は元の例外がスローされたスタックの場所を認識していても、Windows エラー報告に報告されるスタックの場所は最後のスローまたは再スローの場所です。これは、現場で失敗したアプリケーションのデバッグをより困難にするため、残念です。一部の開発者は、これが非常に耐えられないことに気づき、コードを実装する別の方法を選択して、スタック トレースが例外が最初にスローされた場所を正確に反映するようにしました。

private void SomeMethod() {
  Boolean trySucceeds = false;
  try {
    ...
    trySucceeds = true;
  }
  finally {
    if (!trySucceeds) { /* catch code goes in here */ }
  }
}
于 2011-01-19T09:16:47.237 に答える
20

これは、CLR の Windows バージョンでよく知られている制限です。これは、例外処理 (SEH) の Windows 組み込みサポートを使用します。問題は、それがスタック フレーム ベースであり、メソッドにスタック フレームが 1 つしかないことです。内部の try/catch ブロックを別のヘルパー メソッドに移動して、別のスタック フレームを作成することで、問題を簡単に解決できます。この制限のもう 1 つの結果は、JIT コンパイラが try ステートメントを含むメソッドをインライン化しないことです。

于 2010-11-18T18:50:57.823 に答える
10

REAL スタックトレースを保存するにはどうすればよいですか?

新しい例外をスローし、元の例外を内部例外として含めます。

しかし、それは醜いです...長い...スローする正しい例外を選択させます....

あなたは醜い点については間違っていますが、他の2つの点については正しい. 経験則としては、ラップする、変更する、飲み込む、またはログに記録するなど、何かを行う場合を除き、キャッチしないでください。そうすることに決めた場合は、catchそれthrowを使って何かをしていることを確認してください。

また、catch 内にブレークポイントを設定できるように、catch を単純に配置したくなるかもしれませんが、Visual Studio デバッガーには、そのような作業を不要にする十分なオプションが用意されています。代わりに、初回例外または条件付きブレークポイントを使用してみてください。

于 2011-01-19T09:57:57.483 に答える
7

編集/置換

動作は実際には異なりますが、微妙に異なります。動作が異なる理由については、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 /VS2010でテスト済み

于 2011-01-19T09:13:38.233 に答える
5

ここに重複した質問があります。

私が理解しているように-投げます。「rethrow」MSIL 命令にコンパイルされ、スタック トレースの最後のフレームを変更します。

元のスタックトレースを保持し、再スローされた行を追加することを期待しますが、メソッド呼び出しごとにスタックフレームが 1 つしか存在しないようです。

結論: throw の使用は避けてください。再スロー時に例外を新しいものにラップします。これは見苦しくなく、ベストプラクティスです。

于 2011-01-19T10:12:34.633 に答える
5

を使用してスタックトレースを保存できます

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();
    }

出力は次のようになります。

テストアプリ例
テストインナーエクス
   D:\Projects\TestApp\TestApp\Program.cs:line 19 の TestApp.Program.CallAndThrow() で
   D:\Projects\TestApp\TestApp\Program.cs:line 30 の TestApp.Program.Main(String[] args) で
--- 例外がスローされた前の場所からのスタック トレースの終わり ---
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() で
   D:\Projects\TestApp\TestApp\Program.cs:line 38 の TestApp.Program.Main(String[] args) で
--- 例外がスローされた前の場所からのスタック トレースの終わり ---
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() で
   D:\Projects\TestApp\TestApp\Program.cs:line 47 の TestApp.Program.Main(String[] args) で
于 2016-07-12T11:43:25.430 に答える
2

これが意図的なものかどうかはわかりませんが、昔からそうだったと思います。

元の throw new Exception が別のメソッドにある場合、 throw の結果には、元のメソッド名と行番号、および例外が再スローされる main の行番号が含まれている必要があります。

throw ex を使用すると、結果は、例外が再スローされる main の行になります。

つまり、throw ex はすべてのスタック トレースを失いますが、throw はスタック トレースの履歴(つまり、下位レベルのメソッドの詳細) を保持します。ただし、例外が再スローと同じ方法で生成された場合、一部の情報が失われる可能性があります。

注意。非常に単純で小さなテスト プログラムを作成すると、フレームワークによって最適化が行われ、メソッドがインライン コードに変更されることがあります。これは、結果が「実際の」プログラムと異なる場合があることを意味します。

于 2010-11-18T18:27:38.470 に答える
1

あなたの右の行番号が欲しいですか?メソッドごとに1 つのtry/catchを使用するだけです。システムでは、まあ...ロジックやデータアクセスではなく、UIレイヤーだけで、これは非常に面倒です。なぜなら、データベーストランザクションが必要な場合、それらはUIレイヤーにあるべきではなく、正しい行番号ですが、それらが必要ない場合は、キャッチで例外の有無にかかわらず再スローしないでください...

5 分のサンプル コード:

Menu File -> New Project、3 つのボタンを配置し、それぞれに次のコードを呼び出します。

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

走る…最初のボタンが美しい!

于 2012-07-25T17:40:52.070 に答える
0

これは、スタックトレースを変更する場合ではなく、スタックトレースの行番号を決定する方法に関係していると思います。Visual Studio 2010で試してみると、動作は、MSDNドキュメント「throwex;」から期待される動作と似ています。このステートメント「throw;」のポイントからスタックトレースを再構築します。例外が再スローされる場合は常に、行番号が再スローの場所であり、例外が通過した呼び出しではないことを除いて、スタックトレースはそのままにします。

だから「投げる」と。メソッド呼び出しツリーは変更されませんが、行番号は変更される可能性があります。

私はこれに数回遭遇しましたが、それは仕様によるものであり、完全に文書化されていない可能性があります。再スローの場所を知ることは非常に有用であり、メソッドが十分に単純である場合、元のソースは通常とにかく明白であるため、なぜ彼らがこれを行ったのか理解できます。

他の多くの人が言っているように、本当に必要な場合や、その時点で対処する場合を除いて、通常は例外をキャッチしないのが最善です。

興味深い補足事項:Visual Studio 2010では、コンパイル時にゼロ除算エラーが発生するため、質問に示されているコードをビルドすることさえできません。

于 2011-01-19T09:59:51.290 に答える
-2

これは、行 12Exceptionからをキャッチし、行 15でそれを再スローしたためです。そのため、スタック トレースはそれをキャッシュとして受け取り、そこから がスローされました。Exception

例外をより適切に処理するには、単純に を使用try...finallyして、未処理のExceptionバブルを発生させます。

于 2010-11-18T17:17:46.600 に答える