17

質問: C#の単純なthrowステートメント自体で新しい例外が発生することはありますか?


私がこの質問をするのは好奇心からであり、それが重要になる実用的または現実の状況があるからではないことに注意してください。また、私の直感と経験から、答えは「いいえ」であることがわかりますが、その答えを何らかの方法で検証しようとしています (これまでに試したソースの詳細を参照してください)。

私の質問を説明するためのサンプルコードを次に示します。

try
{
    int x = 0, y = 1 / x;
}
catch (Exception outerException)
{

    try
    {
        throw;
    }
    catch (Exception innerException)
    {
        // Q: Does this Assert ever fail??
        System.Diagnostics.Debug.Assert(outerException.Equals(innerException));
    }
}

内部ブロックAssertに触れずに、失敗するように状況を変更する方法があるかどうか疑問に思っています。try/catch

私が試したこと、またはこれに答えようとしていたこと:

  • MSDN のスロー (C# リファレンス)ページを読んでください - 決定的な答えはありません。
  • C# 言語仕様の 5.3.3.11 の部分を確認しました。これは、この種の情報を探すにはおそらく間違った場所です。
  • throw ステートメントでトリガーしようとする可能性がある例外について詳しく説明しました。OutOfMemoryException が頭に浮かびますが、throw.
  • 生成されたコードを確認するために ILDASM を開きました。throwそれが命令に変換されることはわかりますがrethrow、そのステートメントが例外をスローできるかどうかを確認するためにどこを調べればよいかわかりません。

tryこれは、内部ビットについて ILDASM が示すものです。

.try
{
  IL_000d:  nop
  IL_000e:  rethrow
}  // end .try

要約すると、(例外を再スローするために使用される) throw ステートメント自体が例外を引き起こすことはありますか?

4

5 に答える 5

16

私の正直な意見では、理論的にはアサートは「失敗」する可能性があります (実際にはそうは思いません)。

どのように?

注: 以下は、以前に SSCLI で行ったいくつかの調査に基づく私の「意見」です。

  1. InvalidProgramExceptionが発生する可能があります。これは確かに非常にありそうもないことですが、理論的には可能です (たとえば、内部 CLR エラーにより、スロー可能なオブジェクトが使用できなくなる可能性があります!!!!)。
  2. CLR が「再スロー」アクションを処理するのに十分なメモリを見つけられない場合、代わりに OutOfMemoryException をスローします (CLR の内部再スロー ロジックは、OutOfMemoryException のような「事前に割り当てられた」例外を処理していない場合、メモリを割り当てる必要があります)。
  3. CLR が他のホスト (たとえば、SQL サーバーまたは独自のホスト) で実行されていて、ホストが (内部ロジックに基づいて) 例外再スロー スレッドを終了することを決定した場合、ThreadAbortException (この場合は失礼なスレッド アボートと呼ばれます) )が引き上げられます。ただし、この場合にアサートが実行されるかどうかはわかりません。
  4. カスタム ホストがエスカレーション ポリシーを CLR に適用している可能性があります ( ICLRPolicyManager::SetActionOnFailure )。その場合、OutOfMemoryException を処理している場合、エスカレーション ポリシーによって ThreadAbortException が発生する可能性があります (これも失礼なスレッドの中止です。ポリシーが通常のスレッドの中止を指示した場合に何が起こるかはわかりません)。
  5. @Alois Kraus は、「通常の」スレッド アボート例外が発生しないことを明確にしていますが、SSCLI の調査から、(通常の) ThreadAbortException が発生する可能性があることにはまだ疑いがあります。

編集:

前に述べたように、アサートは理論的には失敗する可能性がありますが、実際にはその可能性は非常に低いです。したがって、このための POC を開発することは非常に困難です。より多くの「証拠」を提供するために、以下はrethow、上記のポイントを検証する IL 命令を処理するための SSCLI コードのスニペットです。

警告: 商用 CLR は、SSCLI とは大きく異なる場合があります。

  1. InvalidProgramException :

    if (throwable != NULL)
    {
     ...
    }
    else
    {
        // This can only be the result of bad IL (or some internal EE failure).
        RealCOMPlusThrow(kInvalidProgramException, (UINT)IDS_EE_RETHROW_NOT_ALLOWED);
    }
    
  2. 失礼なスレッドの中止:

    if (pThread->IsRudeAbortInitiated())
    {
        // Nobody should be able to swallow rude thread abort.
        throwable = CLRException::GetPreallocatedRudeThreadAbortException();
    }
    

    これは、「失礼なスレッドの中止」が開始された場合、すべての例外が失礼なスレッドの中止例外に変更されることを意味します。

  3. 最も興味深いのは、OutOfMemoryException. 再スロー IL 命令は基本的に同じ Exception オブジェクトを再スローする (つまり、object.ReferenceEqualstrue を返す) ため、再スロー時に OutOfMemoryException が発生する可能性はないようです。ただし、次の SSCLI コードは、それが可能であることを示しています。

     // Always save the current object in the handle so on rethrow we can reuse it. This is important as it
    // contains stack trace info.
    //
    // Note: we use SafeSetLastThrownObject, which will try to set the throwable and if there are any problems,
    // it will set the throwable to something appropiate (like OOM exception) and return the new
    // exception. Thus, the user's exception object can be replaced here.
    
    throwable = pThread->SafeSetLastThrownObject(throwable);
    

    SafeSetLastThrownObjectSetLastThrownObject を呼び出し、失敗した場合は を発生させOutOfMemoryExceptionます。これがスニペットですSetLastThrownObject(私のコメントが追加されています)

    ...
    if (m_LastThrownObjectHandle != NULL)
    {
       // We'll somtimes use a handle for a preallocated exception object. We should never, ever destroy one of
      // these handles... they'll be destroyed when the Runtime shuts down.
      if (!CLRException::IsPreallocatedExceptionHandle(m_LastThrownObjectHandle))
      {
         //Destroys the GC handle only but not the throwable object itself
         DestroyHandle(m_LastThrownObjectHandle);
      }
    }
    ...
    
    //This step can fail if there is no space left for a new handle
    m_LastThrownObjectHandle = GetDomain()->CreateHandle(throwable);
    

    上記のコード スニペットは、スロー可能なオブジェクトの GC ハンドルが破棄され (つまり、GC テーブルのスロットが解放され)、新しいハンドルが作成されることを示しています。スロットが解放されたばかりであるため、新しいスレッドが適切なタイミングでスケジュールされ、使用可能なすべての GC ハンドルを消費するという非常にまれなシナリオで、コースから外れるまで、新しいハンドルの作成が失敗することはありません。

これとは別に、すべての例外 (再スローを含む) はRaiseException win api を介して発生します。この例外をキャッチして、対応するマネージ例外を準備するコード自体が を発生させる可能性がありますOutOfMemoryException

于 2012-06-26T10:21:18.440 に答える
7

C# の単純な throw ステートメント自体で新しい例外が発生することはありますか?

定義上、そうはなりません。まさにポイントはthrow;、アクティブな例外 (特にスタック トレース) を保持することです。

理論的には、実装によって例外のクローンが作成される可能性がありますが、ポイントは何でしょうか?

于 2012-06-25T19:24:30.847 に答える
5

不足しているビットは、 ECMA-335、パーティション III、セクション 4.24rethrow内にあるの仕様である可能性があると思われます。

4.24 rethrow – 現在の例外を再スローする

説明:
再スロー命令は、catch ハンドラーの本体内でのみ許可されます (パーティション I を参照)。このハンドラーによってキャッチされたのと同じ例外をスローします。再スローは、オブジェクトのスタック トレースを変更しません。

例外:
元の例外がスローされます。

(私のものを強調)

はい、あなたのアサーションは仕様に従って動作することが保証されているようです。(もちろん、これは実装が仕様に従っていることを前提としています...)

C# 仕様の関連部分はセクション 8.9.5 (C# 4 バージョン) です。

式のない throw ステートメントは、catch ブロックでのみ使用できます。その場合、そのステートメントは、その catch ブロックによって現在処理されている例外を再スローします。

これも、元の例外とその例外のみがスローされることを示唆しています。

throw(あなたが参照したセクション5.3.3.11は、ステートメント自体の動作ではなく、明確な代入について話しているだけです。)

もちろん、これはいずれも、どちらかの場所で指定されているものの範囲外の状況に対する Amit のポイントを無効にするものではありません。(ホストが追加のルールを適用する場合、言語仕様でそれらを考慮するのは困難です。)

于 2012-07-18T06:33:58.203 に答える
1

再スローとアサーションの間にコードがないため、アサーションが失敗することはありません。例外をキャッチして別の例外を発生させた場合に例外が変更される唯一の方法-たとえば。バグのあるコードを使用するか、キャッチ句に「新しいものを投げる」ことによって。

于 2012-06-26T01:35:06.893 に答える