10

ネット上、特に SO に関する多くのドキュメントを参照しています。たとえば、C# で例外を再スローする適切な方法は何ですか? 「throw e;」には違いがあるはずです。そして「投げる」。

しかし、から: http://bartdesmet.net/blogs/bart/archive/2006/03/12/3815.aspx

このコード:

using System;

class Ex
{
   public static void Main()
  {
  //
  // First test rethrowing the caught exception variable.
  //
  Console.WriteLine("First test");
  try
  {
     ThrowWithVariable();
  }
  catch (Exception ex)
  {
     Console.WriteLine(ex.StackTrace);
  }

  //
  // Second test performing a blind rethrow.
  //
  Console.WriteLine("Second test");
  try
  {
     ThrowWithoutVariable();
  }
  catch (Exception ex)
  {
     Console.WriteLine(ex.StackTrace);
  }
}

 private static void BadGuy()
 {
   //
   // Some nasty behavior.
  //
   throw new Exception();
 }

   private static void ThrowWithVariable()
 {
   try
   {
         BadGuy();
   }
  catch (Exception ex)
  {
     throw ex;
  }
}

   private static void ThrowWithoutVariable()
{
  try
  {
     BadGuy();
  }
  catch
  {
     throw;
  }
   }
}

次の結果が得られます。

$ /cygdrive/c/Windows/Microsoft.NET/Framework/v4.0.30319/csc.exe Test.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.

$ ./Test.exe
First test
   at Ex.ThrowWithVariable()
   at Ex.Main()
Second test
   at Ex.ThrowWithoutVariable()
   at Ex.Main()

これはブログ投稿と完全に矛盾しています。

http://crazorsharp.blogspot.com/2009/08/rethrowing-exception-without-resetting.htmlのコードでも同じ種類の結果が得られます。

元の質問: 何が間違っていますか?

更新: .Net 3.5 / csc.exe 3.5.30729.4926 と同じ結果

SUMUP : すべての回答が素晴らしかったです。ありがとうございます。

その理由は、64 ビット JITter による効果的なインライン化です。

答えを 1 つだけ選択する必要がありましたが、LukeHの答えを選択した理由は次のとおりです。

  • 彼は、インライン化の問題と、それが私の 64 ビット アーキテクチャに関連している可能性があるという事実を推測しました。

  • 彼は、この動作を回避する最も簡単な方法である NoInlining フラグを提供しました。

しかし、この問題は別の問題を引き起こします。この動作はすべての .Net 仕様 (CLR 仕様と C# プログラミング言語仕様) に準拠していますか?

UPDATE : この最適化は、次のように準拠しているようです: Throw VS rethrow : same result? (ありがとう0xA3 )

よろしくお願いします。

4

5 に答える 5

4

この問題を再現することはできません。.NET 3.5 (32 ビット) を使用すると、Bart の記事で説明されているのと同じ結果が得られます。

私の推測では、.NET 4 コンパイラ/ジッタ (または、これが 3.5 でも発生している場合は 64 ビット コンパイラ/ジッタ) がBadGuyメソッドを呼び出しメソッドにインライン化しています。MethodImpl次の属性を追加してみて、BadGuy違いがあるかどうかを確認してください。

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static void BadGuy()
{
    //
    // Some nasty behavior.
    //
    throw new Exception();
}
于 2010-08-23T22:45:23.487 に答える
3

このコードを自分で実行してみましたが、デバッグ ビルドは期待どおりに動作しましたが、リリース ビルドではあなたと同じ結果になりました。

new Exception();BadGuy()内の唯一のステートメントであるため、コンパイラのインライン化によって単に BadGuy() 呼び出しが throw に置き換えられたのではないかと思います。

プロジェクト プロパティ -> ビルド画面で [コードの最適化] オプションをオフにすると、リリース ビルドとデバッグ ビルドの両方で同じ結果が生成され、スタック トレースの上部に BadGuy() が表示されます。

于 2010-08-23T22:37:41.523 に答える
3

ここでは、JIT オプティマイザが何らかの作業を行っているようです。ご覧のとおり、Debug ビルドを実行すると、2 番目のケースのコール スタックは最初のケースとは異なります。ただし、リリース ビルドでは、最適化により、両方のコール スタックが同一です。

これがジッターに関連していることを確認するには、メソッドをMethodImplAttribute 属性で装飾します。

[MethodImpl(MethodImplOptions.NoOptimization)]
private static void ThrowWithoutVariable()
{
    try
    {
        BadGuy();
    }
    catch
    {
        throw;
    }
}

と の IL はまだ異なることに注意してThrowWithoutVariableくださいThrowWithVariable

.method private hidebysig static void  ThrowWithVariable() cil managed
{
  // Code size       11 (0xb)
  .maxstack  1
  .locals init ([0] class [mscorlib]System.Exception ex)
  .try
  {
    IL_0000:  call       void Ex::BadGuy()
    IL_0005:  leave.s    IL_000a
  }  // end .try
  catch [mscorlib]System.Exception 
  {
    IL_0007:  stloc.0
    IL_0008:  ldloc.0
    IL_0009:  throw
  }  // end handler
  IL_000a:  ret
} // end of method Ex::ThrowWithVariable

.method private hidebysig static void  ThrowWithoutVariable() cil managed
{
  // Code size       11 (0xb)
  .maxstack  1
  .try
  {
    IL_0000:  call       void Ex::BadGuy()
    IL_0005:  leave.s    IL_000a
  }  // end .try
  catch [mscorlib]System.Object 
  {
    IL_0007:  pop
    IL_0008:  rethrow
  }  // end handler
  IL_000a:  ret
} // end of method Ex::ThrowWithoutVariable

これが CLI 仕様に準拠しているかどうかのフォローアップの質問に答えるために更新します

実際、準拠しています。つまり、JIT コンパイラーが重要な最適化を有効にできるようにします。附属書 Fの 52 ページには次のように記載されています (私が強調):

一部の CIL 命令は、メモリと型の安全性を保証する暗黙の実行時チェックを実行します。もともと、CLI は例外が正確であることを保証していました。つまり、例外がスローされたときにプログラムの状態が保持されていました。ただし、暗黙的なチェックに正確な例外を適用すると、いくつかの重要な最適化を実際に適用できなくなります。 プログラマーは、カスタム属性を介して、メソッドが「緩和」されていることを宣言できるようになりました。これは、暗黙的な実行時チェックから発生する例外が正確である必要がないことを示しています。

緩和されたチェックは、(メモリと型の安全性を維持することによって) 検証可能性を維持しながら、命令を並べ替える最適化を許可します。特に、次の最適化が可能になります。

  • 暗黙的な実行時チェックをループから巻き上げます。
  • ループ反復の並べ替え (ベクトル化や自動マルチスレッド化など)
  • 交換ループ
  • インライン化されたメソッドを同等のマクロと同じくらい高速にするインライン化
于 2010-08-23T22:38:12.117 に答える
1

デバッグ ビルドを使用すると、違いがより明確になります。デバッグ ビルドを使用すると、最初の実行では場所がthrow ex行として表示され、2 回目は への実際の呼び出しから発信された場所として表示されBadGuyます。明らかに、'問題' は BadGuy への呼び出しです。throw ex ラインではありません。直接throw;ステートメントを使用すると、追跡するゴーストが少なくなります。

この浅いスタック トレースでは、利点はすぐには明らかではありません。非常に深いスタックでは、組み込みの re-throw ステートメントを使用する代わりに手動で例外をスローすることで、問題の実際の原因を隠し、忠実度をいくらか失います。

于 2010-08-23T22:36:17.950 に答える
0

余談ですが、一度ブログに投稿されたハックを見つけました(それ以来、参照を失いました)。これにより、再スロー時にコールスタックを保持できます。これは主に、あるコンテキスト (たとえば、非同期操作を実行しているスレッド) で例外をキャッチし、別のコンテキスト (たとえば、非同期操作を開始した別のスレッド) でそれを再スローする場合に役立ちます。リモート処理の境界を越えてスタック トレースを保存できるようにするために、ドキュメントに記載されていない機能がいくつか含まれています。

    //This terrible hack makes sure track trace is preserved if exception is re-thrown
    internal static Exception AppendStackTrace(Exception ex)
    {
        //Fool CLR into appending stack trace information when the exception is re-thrown
        var remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString",
                                                                 BindingFlags.Instance |
                                                                 BindingFlags.NonPublic);
        if (remoteStackTraceString != null)
            remoteStackTraceString.SetValue(ex, ex.StackTrace + Environment.NewLine);

        return ex;
    }
于 2010-08-23T22:47:16.590 に答える