CLR 徹底解剖

破損状態例外を処理する

Andrew Pardoe

このコラムは、Visual Studio 2010 のプレリリース バージョンに基づいています。記載されているすべての情報は、変更される場合があります。

目次

例外とは正確にどのようなものか
Win32 SEH 例外と System.Exception
マネージ コードと SEH
破損状態例外
それでも Catch (Exception e) を使用するのはよくない
賢明なコーディング

完全には正しくないが、ほぼ十分と言えるコードを作成したことがありますか。すべてがうまく行っているときには正しく動作するが、何か問題が発生した場合にどうなるかわからないコードを作成したことはありますか。記述したコードまたはメンテナンスする必要があったコードにおそらく含まれていると考えられるものに、単純で不適切なステートメント (catch (Exception e)) があります。この小さなステートメントは、無害で簡単に見えますが、予想外の状況が発生した場合に多くの問題を引き起こす可能性があります。

次のコードで行っているような方法で例外を使用するコードを見かけたことがある場合は、このコラムを読む必要があります。

public void FileSave(String name)
{
    try
    {
      FileStream fs = new FileStream(name, FileMode.Create);
    }
    catch (Exception)
    {
      throw new System.IO.IOException("File Open Error!");
    }
}

このコードのエラーはよく見られるものです。try ブロックで実行しているコードによって発生する可能性のある例外だけをキャッチするよりも、すべての例外をキャッチするコードを記述する方が簡単です。しかし、例外階層のベースをキャッチすると、すべてのタイプの例外を飲み込み、それを IOException に変換することになります。

例外処理は、ほとんどの人が、詳細を理解しているというよりは実用的な知識を持っている領域の 1 つです。まず、ネイティブ プログラミングや古い大学の教科書から例外処理について学んだ方のために、CLR の観点から例外処理について説明する背景情報を少し示します。マネージ例外処理についてよく知っているベテランの方は、異なる種類の例外のセクション、またはマネージ コードでの例外処理と構造化例外処理 (SEH) のセクションにお進みください。ただし、最後のいくつかのセクションは必ずお読みください。

例外とは正確にどのようなものか

例外は、プログラム スレッドの正常な実行では予想されなかった条件が検出された場合に生成されるシグナルです。多くのエージェントは、不適切な条件を検出して例外を生成できます。プログラム コード (またはそこで使用されるライブラリ コード) は、System.Exception から派生する型をスローできます。CLR 実行エンジンは例外を生成でき、アンマネージ コードも例外を生成できます。実行スレッドで発生した例外は、AppDomain 間でネイティブおよびマネージ コードを通じてスレッドをたどり、プログラムで処理されない場合はオペレーティング システムによって処理不能例外として扱われます。

例外は、何か問題が発生したことを示します。すべてのマネージ例外には型 (System.ArgumentException や System.ArithmeticException など) がありますが、型は例外が発生したコンテキストでのみ意味を持ちます。プログラムでは、例外が発生する原因となった条件を認識している場合に例外を処理できます。しかし、プログラムが例外を処理しない場合は、あらゆる数の問題を示している可能性があります。また、例外がプログラムを離れた後は、非常に汎用的な意味しか持ちません。つまり、何か問題が発生したということです。

Windows は、プログラムで例外が処理されないことを検出した場合に、プロセスを終了することでプログラムの永続データ (ディスク上のファイル、レジストリ設定など) を保護しようとします。例外が本来はあまり問題のない予想外のプログラム状態 (空のスタックからのポップに失敗したなど) を示していた場合でも、オペレーティング システムには例外を正しく解釈するコンテキストがないため、Windows がその例外を検出したときには重大な問題であるかのように見えます。1 つの AppDomain 内の 1 つのスレッドが、例外を処理しないことで CLR インスタンス全体をダウンさせることがあります (図 1 を参照)。

fig01.gif

図 1 1 つのスレッドの処理不能例外によってプロセス全体が終了する

例外がそれほど危険なものであるとしたら、なぜ普及しているのでしょうか。オートバイやチェーンソーのように、例外はその実力によって非常に役立つものになります。プログラム スレッド上の正常なデータフローは、呼び出しとリターンを通じて関数から関数に移動します。関数の呼び出しごとにスタック上に実行のフレームが作成され、リターンごとにそのフレームが破棄されます。グローバル状態の変更以外に、連続するフレーム間でデータを関数パラメータまたは戻り値として渡すことで実現されるのは、プログラム内のデータのフローだけです。例外処理がないと、すべての呼び出し元で、呼び出された関数の正常終了を確認する (または、単純に常にすべて正常であると想定する) 必要があります。

Windows では例外処理を使用しないため、ほとんどの Win32 API は、エラーを示すためにゼロ以外の値を返します。プログラマは、呼び出された関数の戻り値をチェックするコードですべての関数呼び出しをラップする必要があります。たとえば、ディレクトリ内のファイルを一覧表示する方法に関する MSDN ドキュメントから抽出した次のコードは、各呼び出しで正常終了を明示的にチェックします。FindNextFile(...) の呼び出しがチェックにラップされて、戻り値がゼロ以外かどうかが確認されます。呼び出しが正常に終了しなかった場合は、別の関数呼び出し (GetLastError()) が例外条件の詳細を提供します。戻り値は必然的にローカル関数のスコープに制限されるため、次のフレームで、各呼び出しの正常終了をチェックする必要があります。

// FindNextFile requires checking for success of each call 
while (FindNextFile(hFind, &ffd) != 0); 
dwError = GetLastError(); 
if (dwError != ERROR_NO_MORE_FILES) 
{ 
  ErrorHandler(TEXT("FindFirstFile")); 
} 
FindClose(hFind); 
return dwError; 

エラー条件は、予期しない条件を含む関数から、その関数の呼び出し元にのみ渡すことができます。例外には、予期しない条件の処理方法を認識しているフレームに到達するまで、現在の関数のスコープからスタックのすべての上位フレームに関数の実行結果を渡す機能があります。CLR の例外システム (2 パス例外システムと呼ばれます) は、呼び出し元から開始し、ある関数で例外が処理されることが示されるまで、スレッドのコール スタック上のすべての先祖に例外を渡します (これを第 1 パスと呼びます)。

例外システムは、例外が発生した場所から処理される場所までの間にあるコール スタック上の各フレームの状態をアンワインドします (第 2 パスと呼びます)。スタックがアンワインドされると、CLR は、アンワインドされた各フレームの finally 句と fault 句の両方を実行します。さらに、処理フレームの catch 句が実行されます。

CLR は、コール スタック上のすべての先祖をチェックするため、呼び出し元に catch ブロックは不要です。例外はスタックの上位のどの場所でもキャッチできます。コードですべての関数呼び出しの結果を即時にチェックする代わりに、プログラマは、例外が発生した場所から離れた場所でエラーを処理できます。エラー コードを使用するには、プログラマがすべてのスタック フレームでエラー コードを調べて、エラー条件を処理できる場所に到達するまでエラー コードを渡す必要があります。例外処理によって、プログラマはスタック上のすべてのフレームで例外を調べる必要がなくなります。

カスタム例外型のスローの詳細については、「エラー処理 : マネージ COM+ サーバー アプリケーションからカスタム例外型をスローする」を参照してください。

Win32 SEH 例外と System.Exception

例外が発生した場所から離れた場所で例外をキャッチする機能には、興味深い副作用があります。プログラム スレッドは、例外が発生した場所を認識していなくても、コール スタック上の任意のアクティブなフレームからプログラム例外を受け取ることができます。ただし、例外は必ずしもプログラムで検出されたエラー条件を表していません。プログラム スレッドがプログラムの外部で例外を発生させることもあります。

スレッドの実行によってプロセッサに障害が発生した場合は、制御がオペレーティング システム カーネルに移り、障害が SEH 例外としてスレッドに提示されます。catch ブロックは、スレッドのスタック上のどこで例外が発生したかを認識しないのと同様に、OS カーネルが SEH 例外を生成したポイントについても正確に認識する必要がありません。

Windows は、SEH を使用して、OS 例外についてプログラム スレッドに通知します。CLR は通常 SEH 例外で示されているエラーの種類を防ぐため、マネージ コードのプログラマがこのような例外を見ることはめったにありません。ただし、Windows が SEH 例外を生成した場合、CLR はマネージ コードにその例外を渡します。マネージ コードで SEH 例外が発生することはまれですが、安全でないマネージ コードは、プログラムが無効なメモリへのアクセスを試行したことを示す STATUS_ACCESS_VIOLATION を生成することがあります。

SEH の詳細については、Microsoft Systems Journal の 1997 年 1 月号の Matt Pietrek の記事「Win32 構造化例外処理の詳細に関する速習講座」を参照してください。

SEH 例外は、プログラムで発生する例外とは異なるクラスです。プログラムは、空のスタックからアイテムを取り出そうとしたり、存在しないファイルを開こうとしたりしたために例外を生成することがあります。これらすべての例外は、プログラムの実行のコンテキストで意味を持ちます。SEH 例外は、プログラムの外部のコンテキストを表します。たとえば、アクセス違反 (AV) は無効なメモリへの書き込みの試行を示します。プログラム エラーとは異なり、SEH 例外はランタイムのプロセスの整合性が損なわれた可能性があることを示します。しかし、SEH 例外が System.Exception から派生する例外とは異なっていても、CLR が SEH 例外をマネージ スレッドに渡す場合、これは catch (Exception e) ステートメントでキャッチできます。

一部のシステムは、この 2 種類の例外を分離しようとします。Microsoft Visual C++ コンパイラでは、/EH スイッチを指定してプログラムをコンパイルした場合に、C++ throw ステートメントで発生した例外と Win32 SEH 例外が区別されます。通常のプログラムは自身で生成していないエラーの処理方法を認識しないため、この分離が役に立ちます。C++ プログラムは、std::vector に要素を追加しようとする場合に、メモリ不足により操作が失敗する可能性があることを予期する必要があります。しかし、適切に記述されたライブラリを使用している正しいプログラムでアクセス違反が処理されることを期待してはなりません。

この分離は、プログラマの役に立ちます。AV は重大な問題です。クリティカルなシステム メモリへの予期しない書き込みは、プロセスのどの部分にも予測できない影響を与える可能性があります。ただし、間違った (そしてチェックされない) ユーザー入力によるゼロ除算エラーなど、重大度の低い SEH エラーもあります。ゼロ除算が含まれているプログラムは不適切ですが、これがシステムの他の部分に影響を与える可能性は低くなります。実際に、C++ プログラムは、システムの他の部分を不安定にすることなく、ゼロ除算エラーを処理できる可能性があります。したがって、この分離は役立ちますが、マネージ プログラマが必要とするセマンティクスを表しているとは言えません。

マネージ コードと SEH

CLR は常に、プログラム自体で生成された例外と同じメカニズムを使用して SEH 例外をマネージ コードに渡してきました。これは、コードが合理的に処理できない例外条件を処理しようとしない限り、問題にはなりません。ほとんどのプログラムでは、アクセス違反の後に実行を安全に続行できません。あいにく、CLR の例外処理モデルは、System.Exception 階層の最上位でプログラムが例外をキャッチできるようにすることで、これらの重大エラーをキャッチすることをユーザーに常に促してきました。しかし、この処理が適切であることはまれです。

処理不能例外は深刻な結果をもたらすため、catch (Exception e) の記述はよくあるプログラミング エラーです。しかし、関数によってどのようなエラーが生成されるかわからない場合は、プログラムでその関数を呼び出すときに考えられるすべてのエラーから保護する必要があるという意見があるかもしれません。これは、プロセスが破損しているかもしれない状態で実行を続行することの意味について考えるまでは、妥当なアクションであるように思えます。中止して再試行することが最適な方法である場合があります。Watson のダイアログを見たいと思う人はいませんが、データが破損するよりもプログラムを再起動する方がよいのです。

認識していないコンテキストから発生した例外をキャッチするプログラムは、重大な問題です。しかし、例外仕様またはその他のコントラクト メカニズムを使用して問題を解決することはできません。また、CLR は多くの種類のアプリケーションおよびホストのためのプラットフォームであるため、マネージ プログラムが SEH 例外の通知を受信できることが重要です。SQL Server などの一部のホストは、アプリケーションのプロセスに対する全体的な制御権を持つ必要があります。ネイティブ コードと相互運用するマネージ コードでは、ネイティブ C++ 例外または SEH 例外の処理が必要な場合があります。

しかし、catch (Exception e) を記述するプログラマのほとんどは、実際にアクセス違反をキャッチしたいわけではありません。致命的なエラーが発生した場合は、プログラムを不明な状態でもたつかせるよりも、プログラムの実行を停止する方を好みます。このことは、Visual Studio や Microsoft Office など、マネージ アドインをホストするプログラムに特に当てはまります。アドインによってアクセス違反が発生し、例外が飲み込まれた場合、ホストは問題が起きているのを認識することなく自身の状態 (またはユーザー ファイル) に損傷を与えることがあります。

CLR の Version 4 で、製品チームは、破損したプロセス状態が他のすべての例外と異なることを示す例外を作成しています。数十個の SEH 例外について、破損したプロセス状態を示すように指定しています。この指定は、例外の型そのものとは対照的に、例外が発生したコンテキストに関連しています。したがって、Windows から受信したアクセス違反は破損状態例外 (CSE) としてマークされますが、新しい System.AccessViolationException のスローを記述することでユーザー コードで発生した例外は CSE としてマークされません。PDC 2008 に参加した方は、これらの変更を含む Visual Studio 2010 の Community Technology Preview をお持ちです。

例外はプロセスを破損しないことに注意してください。プロセス状態で破損が検出された後に、例外が生成されます。たとえば、安全でないコードのポインタを介する書き込みがプログラムに属していないメモリを参照する場合は、アクセス違反が発生します。無効な書き込みは実際には発生していません。オペレーティング システムがメモリの所有権をチェックし、アクションの発生を防いでいます。アクセス違反は、ポインタ自体がスレッドの実行の初期に破損したことを示します。

破損状態例外

Version 4 以降では、CLR 例外システムは、コードがプロセス破損状態例外を処理できることを明示的に示していない限り、CSE をマネージ コードに渡しません。このため、マネージ コード内の catch (Exception e) のインスタンスには、CSE が提示されません。CLR 例外システムの内部を変更することで、例外階層を変更したり、マネージ言語の例外処理セマンティクスを変更したりする必要がなくなります。

互換性の理由から、CLR チームは次のように、以前の動作で以前のコードを実行する方法を提供しました。

  • ソースを更新する必要なしに、Microsoft .NET Framework 3.5 で作成されたコードを再コンパイルし、.NET Framework 4.0 で実行する必要がある場合は、アプリケーション構成ファイルに legacyCorruptedStateExceptionsPolicy=true というエントリを追加できます。
  • .NET Framework 3.5 または以前のバージョンのランタイム用にコンパイルされたアセンブリは、.NET Framework 4.0 で実行するときに、破損状態例外を処理できます (つまり、以前の動作が維持されます)。

コードで CSE を処理する必要がある場合は、例外の句 (catch、finally、または fault) を含む関数を新しい属性 System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions でマークすることにより、その意図を示す必要があります。CSE が発生した場合、CLR は一致する catch 句の検索を実行しますが、HandleProcessCorruptedStateExceptions 属性でマークされた関数しか検索しません (図 2 を参照)。

図 2 HandleProcessCorruptedStateExceptions を使用する

// This program runs as part of an automated test system so you need
// to prevent the normal Unhandled Exception behavior (Watson dialog).
// Instead, print out any exceptions and exit with an error code.
[HandledProcessCorruptedStateExceptions]
public static int Main()
{
    try
    {
        // Catch any exceptions leaking out of the program
        CallMainProgramLoop();
    }
    catch (Exception e) // We could be catching anything here
    {
        // The exception we caught could have been a program error
        // or something much more serious. Regardless, we know that
        // something is not right. We'll just output the exception
        // and exit with an error. We won't try to do any work when 
        // the program or process is in an unknown state!
        System.Console.WriteLine(e.Message);
        return 1;
    }
    return 0;
  } 

適切な catch 句が見つかった場合、CLR はスタックを通常どおりアンワインドしますが、属性でマークされた関数の finally および fault ブロック (また C# では、using ステートメントを使用した暗黙的な finally ブロック) しか実行しません。HandleProcessCorruptedStateExceptions 属性は、部分的に信頼されているコードまたは透過的なコードで検出された場合に無視されます。信頼されたホストは、信頼されていないアドインがこれらの重大な例外をキャッチして無視することを望まないためです。

それでも Catch (Exception e) を使用するのはよくない

CLR 例外システムは、最悪の例外を CSE としてマークしますが、それでもコードに catch (Exception e) を記述するのは賢明ではありません。例外は、予期しない状況を全面的に表します。CLR は、最悪の例外、つまり破損している可能性のあるプロセス状態を示す SEH 例外を検出できます。ただし、他の予期しない条件も、無視したり、汎用的に処理したりすれば悪影響を及ぼすことがあります。

プロセスの破損がない場合、CLR はプログラムの正常性とメモリの安全性をかなり強力に保証します。安全な Microsoft 中間言語 (MSIL) コードで記述されたプログラムを実行する場合は、プログラム内のすべての命令が正しく実行されることを確信できます。しかし、多くの場合、プログラムの命令で指示されている処理の実行は、プログラマが望んでいる処理の実行と異なります。CLR によれば完全に正しいプログラムが、ディスクに書き込まれたプログラム ファイルなどの永続的な状態を破壊することがあります。

高校のテストの点数のデータベースを管理するプログラムを単純な例として考えてみましょう。プログラムでは、オブジェクト指向のデザイン原理を使用して、データをカプセル化し、予期しないイベントを示すマネージ例外を生成します。ある日、学校の事務員が、成績ファイルを生成するときに Enter キーを 1 回多く押しました。プログラムは、空のキューから値を取り出そうとし、コールスタック上のフレームで処理されない QueueEmptyException を生成します。

スタックの上部に近い場所に、例外をキャッチする try/catch 句を持つ関数 GenerateGrades() があります。あいにく、GenerateGrades() は、生徒がキューに格納されていることを認識せず、QueueEmptyException で行うべき処理を認識していません。しかし、GenerateGrades() を記述したプログラマは、これまでに計算されたデータを保存せずにプログラムをクラッシュさせることは望んでいません。すべてがディスクに安全に書き込まれてからプログラムが終了します。

このプログラムの問題は、間違っている可能性のある多くの仮定が行われていることです。生徒のキュー内の不足エントリが最後にあるというのはどうでしょうか。場合によっては最初の生徒レコードまたは 10 番目の生徒レコードがスキップされています。例外は、プログラムが正しくないことをプログラマに示すだけです。ディスクへのデータの保存や、"リカバリ" と実行の続行などのアクションを実行することは、明らかに間違っています。例外が発生したコンテキストを認識していなければ、正しいアクションは実行できません。

プログラムは、例外の発生場所の近くで特定の例外をキャッチした場合に、正しいアクションを実行できる場合があります。プログラムは、生徒のデキューを試行する関数内で QueueEmptyException が何を意味するかを認識しています。関数が例外型のクラス全体をキャッチするのではなく、例外を型別にキャッチすれば、プログラムを正常な状態にしようとする試みがはるかに行いやすくなります。

一般に、特定の例外をキャッチすることは、例外ハンドラにほとんどのコンテキストを提供するため正しい処理です。コードが 2 つの例外をキャッチする可能性がある場合は、その両方を処理できる必要があります。catch (Exception e) を使用したコードの記述では、文字どおりすべての例外的な状況を処理できる必要があります。これは守るのが非常に難しい約束事です。

一部の言語は、プログラマが広範な例外クラスをキャッチすることを防ごうとしています。たとえば、C++ には例外仕様があります。これは、その関数でどのような例外が発生する可能性があるかをプログラマが指定できるようにするメカニズムです。Java は、コンパイル時に特定の例外クラスが指定されていることを要求するチェック例外で、これをさらに一歩進めています。どちらの言語でも、関数宣言でこの関数の外部に流れる可能性のある例外の一覧を示し、呼び出し元がこれらの例外を処理する必要があります。例外仕様は優れたアイデアですが、実際には相反する結果が生じます。

マネージ コードで CSE を処理できる必要があるかどうかについては、活発な議論がなされています。これらの例外は、通常はシステムレベルのエラーを示し、システムレベルのコンテキストを認識するコードでのみ処理する必要があります。ほとんどの人にとって CSE を処理できる必要はありませんが、この処理が必要になるシナリオがいくつかあります。

1 つのシナリオは、例外が発生した場所の非常に近くにいる場合です。たとえば、バグが多いことが知られているネイティブ コードを呼び出すプログラムについて考えます。コードをデバッグすると、アクセスする前にポインタをゼロにし、アクセス違反が引き起こされることがあるとわかります。ポインタ破損の原因がわかっており、プロセスの整合性を維持することが不安を解消するため、P/Invoke を使用してネイティブ コードを呼び出す関数では HandleProcessCorruptedStateExceptions 属性を使用する必要があります。

この属性の使用が必要になる可能性のある他のシナリオとして、エラーから可能な限り離れた場所にいる場合があります。実際に、プロセスを終了する準備がほぼ整っています。エラーが発生した場合にカスタム ログの記録を実行する必要のあるホストまたはフレームワークを作成したとしましょう。メイン関数を try/catch/finally ブロックでラップし、HandleProcessCorruptedStateExceptions でマークできます。エラーによって予期せずプログラムのメイン関数まで進んだ場合、ほとんど何も作業する必要なくログにデータをいくつか書き込み、プロセスを終了します。プロセスの整合性に問題がある場合、実行する作業は危険にさらされる可能性がありますが、カスタム ログに失敗したときに、それが良い結果となる場合があります。

図 3 に示すダイアグラムをご覧ください。ここでは、関数 1 (fn1()) に HandleProcessCorruptedStateExceptions の属性が付けられているため、その catch 句はアクセス違反をキャッチします。関数 3 の finally ブロックは、例外が関数 1 でキャッチされる場合でも実行されません。スタックの一番下にある関数 4 がアクセス違反を生成します。

fig03.gif

図 3 例外とアクセス違反

これらのいずれのシナリオでも、処理が完全に安全に行われるという保証はありませんが、プロセスの単純な終了が許容されないシナリオがあります。ただし、CSE を処理することに決めた場合、それを正しく行うという大きな負担がプログラマにかかります。CLR 例外システムは、第 1 パス (一致する catch 句を検索する) または第 2 パス (各フレームの状態をアンワインドし、finally および fault ブロックを実行する) のいずれにおいても、新しい属性でマークされていない関数に CSE を渡さないことに注意してください。

finally ブロックは、例外があるかどうかにかかわらず、コードが常に実行されることを保証するために存在します (fault ブロックは、例外が発生した場合にのみ実行されますが、常に実行されるという同様の保証があります)。これらのコンストラクトは、ファイル ハンドルの解放や偽装コンテキストを元に戻す処理など、クリティカル リソースのクリーンアップに使用されます。

制約された実行領域 (CER) を使用して信頼性を高めるように記述されたコードでも、HandleProcessCorruptedStateExceptions 属性でマークされた関数内に含まれていない限り、CSE の発生時には実行されません。CSE を処理し、プロセスの安全な実行を継続する適切なコードを記述することは非常に困難です。

図 4 のコードをよく見て、問題が起こる可能性のある部分を確認してください。このコードが CSE を処理できる関数に含まれていない場合、アクセス違反の発生時に finally ブロックは実行されません。プロセスが終了する場合は問題ありません。開いているファイル ハンドルは解放されます。しかし、他のコードがアクセス違反をキャッチし、状態の復元を試行する場合は、このファイルを閉じて、このプログラムが変更した他の外部状態を復元する必要があることを認識する必要があります。

図 4 finally ブロックは実行されない可能性がある

void ReadFile(int index)
    {
      System.IO.StreamReader file = 
        new System.IO.StreamReader(filepath);
          try
          {
            file.ReadBlock(buffer, index, buffer.Length);
          }
          catch (System.IO.IOException e)
          {
            Console.WriteLine("File Read Error!");
          }
          finally
          {
            if (file != null)
                {
                    file.Close()
                }
          }
    }

CSE を処理することに決めた場合、コードでは、アンワインドされていない多数のクリティカルな状態があることを予期する必要があります。finally および fault ブロックは実行されていません。制約された実行領域は実行されていません。プログラムおよびプロセスは不明な状態にあります。

自分のコードが正しい処理を行うことがわかっている場合は、何をすればよいのか明らかです。しかし、プログラムが実行されている状態が不明な場合は、プロセスを終了する方がよいでしょう。または、アプリケーションがホストされている場合は、ホストで指定されたエスカレーション ポリシーを呼び出します。信頼性のあるコードの記述と CER の詳細については、2007 年 12 月号の Alessandro Catorcini と Brian Grunkemeyer のコラム「CLR 徹底解剖」を参照してください。

賢明なコーディング

CLR によって CSE のネイティブなキャッチは阻止されますが、過度に広い例外クラスをキャッチするのは賢明ではありません。しかし、catch (Exception e) は多くのコードに見られ、これが変更される可能性はあまりありません。破損したプロセス状態を表す例外を、すべての例外をネイティブにキャッチするコードに渡さないことにより、このコードが重大な状況を悪化させることを防ぎます。

例外をキャッチするコードを次回作成またはメンテナンスするときには、例外が何を意味するかについて考えてください。キャッチした型は、プログラム (および使用されるライブラリ) で文書化されているスローの型と一致しているでしょうか。プログラムが実行を正しく安全に続行できるように例外を処理する方法を理解しているでしょうか。

例外処理は、慎重に熟考のうえ使用する必要のある強力なツールです。この機能を本当に使用する必要がある場合 (破損したプロセスを示している可能性のある例外を本当に処理する必要がある場合)、CLR はプログラマを信頼してその処理を任せます。慎重に正しく行ってください。

ご意見やご質問は clrinout@microsoft.com までお送りください。

Andrew Pardoe は、マイクロソフトで CLR のプログラム マネージャを務めています。彼は、デスクトップおよび Silverlight ランタイム両方の実行エンジン全般に携わっています。連絡先は Andrew.Pardoe@microsoft.com です。