DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

捕捉されなかった例外がスローされたことを知る

ここでは、Try...Catch...で捕捉(キャッチ、ハンドル、トラップ)されなかった例外(エラー)がスローされたときに、その例外の情報を知るための方法を紹介します。

Application.ThreadExceptionイベントを使用する方法

Windows Forms アプリケーションでは、捕捉されなかった例外がスローされるとApplication.ThreadExceptionイベントが発生します。

以下に例を示します。ここではフォームのLoadイベントハンドラでThreadExceptionイベントハンドラを追加していますが、実際には、エントリポイントの適当な位置(Application.Runメソッドを呼び出す前)に記述したほうがよいでしょう。エントリポイントについて詳しくは、こちらをご覧ください。

VB.NET
コードを隠すコードを選択
'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) _
        Handles MyBase.Load
    'ThreadExceptionイベントハンドラを追加
    AddHandler Application.ThreadException, _
        AddressOf Application_ThreadException
End Sub

'ThreadExceptionイベントハンドラ
Public Sub Application_ThreadException(ByVal sender As Object, _
        ByVal e As System.Threading.ThreadExceptionEventArgs)
    Try
        'エラーメッセージを表示する
        MessageBox.Show(e.Exception.Message, "エラー")
    Finally
        'アプリケーションを終了する
        Application.Exit()
    End Try
End Sub
C#
コードを隠すコードを選択
//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, EventArgs e)
{
    //ThreadExceptionイベントハンドラを追加
    Application.ThreadException +=
        new System.Threading.ThreadExceptionEventHandler(
            Application_ThreadException);
}

//ThreadExceptionイベントハンドラ
private void Application_ThreadException(object sender,
    System.Threading.ThreadExceptionEventArgs e)
{
    try
    {
        //エラーメッセージを表示する
        MessageBox.Show(e.Exception.Message, "エラー");
    }
    finally
    {
        //アプリケーションを終了する
        Application.Exit();
    }
}

//Button1のClickイベントハンドラ
private void Button1_Click(object sender, EventArgs e)
{
    throw new ApplicationException("テストです。");
}

上記のコードを実行すると、Button1がクリックされて例外がスローされたときにメッセージボックスにメッセージが表示されて(「テストです。」と表示される)、アプリケーションが終了します。

ThreadExceptionイベントハンドラでは、通常、次のような処理を行います。

  • ユーザーにエラーが発生したことを伝える。
  • 必要ならば、例外情報をログに保存する。
  • 可能ならば、アプリケーション終了時の処理(設定の保存など)を行う。
  • アプリケーションを終了する。

ThreadExceptionイベントを使う場合は、次のような点に注意する必要があります。

  • Windows Forms アプリケーションでのみ使用できます。
  • このイベントが発生するのは、メインスレッドで例外がスローされた時のみです。
  • 上記のコードをVisual Studioの「デバッグ開始」で実行したときは、ThreadExceptionイベントが発生せずに、デバッガのダイアログが表示されます。Visual Studio 2003以前では、Application.Runメソッドを呼び出す前(フォームのインスタンスを作成する前)にThreadExceptionイベントハンドラを追加することにより、ThreadExceptionイベントが発生するようになります。2005では、こうしても解決されません(解決されるという場合もあるようです)。デバッガダイアログを表示せずにThreadExceptionイベントを確認するには、メニューの「デバッグ」-「デバッグなしで開始」で実行します。
  • ThreadExceptionイベントハンドラ内でアプリケーションを終了させるコードを記述しないとアプリケーションは終了しません。
  • サポート技術情報 KB915322によると、.NET Framework 1.1 SP1でApplication.ThreadExceptionイベントが発生しないケースがあるようです。
  • .NET Framework 2.0以降では、Application.SetUnhandledExceptionModeメソッドにより、アプリケーション構成ファイルに関係なく、常にThreadExceptionを発生されるか(CatchExceptionを指定)、あるいはさせないか(ThrowExceptionを指定)を設定することができます。SetUnhandledExceptionModeメソッドは、Application.Runの前に呼び出します。
補足:SetUnhandledExceptionModeメソッドは、2番目のパラメータにFalseを指定することにより、アプリケーション例外モードとすることができるようです(デフォルトは、スレッド例外モード)。しかし現在は、SetUnhandledExceptionModeメソッドの2番目のパラメータをFalseにして呼び出すと、例外がスローされます。

AppDomain.UnhandledExceptionイベントを使用する方法

AppDomain.UnhandledExceptionイベントによっても捕捉されていない例外を調べることができます。

以下に例を示します。Application.ThreadExceptionイベントの例をUnhandledExceptionイベント用に書き換えただけです。こちらもやはりUnhandledExceptionイベントハンドラを追加するコードをエントリポイントに記述したほうがよいでしょう。

VB.NET
コードを隠すコードを選択
'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) _
        Handles MyBase.Load
    'UnhandledExceptionイベントハンドラを追加
    AddHandler System.AppDomain.CurrentDomain.UnhandledException, _
        AddressOf CurrentDomain_UnhandledException

    'または、次のようにもできる
    'Dim curDom As System.AppDomain = System.Threading.Thread.GetDomain()
    'AddHandler curDom.UnhandledException, _
    '    AddressOf CurrentDomain_UnhandledException
End Sub

'UnhandledExceptionイベントハンドラ
Private Sub CurrentDomain_UnhandledException(ByVal sender As Object, _
        ByVal e As UnhandledExceptionEventArgs)
    Try
        Dim ex As Exception = CType(e.ExceptionObject, Exception)
        'エラーメッセージを表示する
        MessageBox.Show(ex.Message, "エラー")
    Finally
        'アプリケーションを終了する
        Application.Exit()
    End Try
End Sub
C#
コードを隠すコードを選択
//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, EventArgs e)
{
    //UnhandledExceptionイベントハンドラを追加
    System.AppDomain.CurrentDomain.UnhandledException +=
        new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

    //または、次のようにもできる
    //System.AppDomain curDom = System.Threading.Thread.GetDomain();
    //curDom.UnhandledException +=
    //    new UnhandledExceptionEventHandler(
    //        CurrentDomain_UnhandledException);
}

//UnhandledExceptionイベントハンドラ
private void CurrentDomain_UnhandledException(object sender,
    UnhandledExceptionEventArgs e)
{
    try
    {
        Exception ex = (Exception)e.ExceptionObject;
        //エラーメッセージを表示する
        MessageBox.Show(ex.Message, "エラー");
    }
    finally
    {
        //アプリケーションを終了する
        Application.Exit();
    }
}

UnhandledExceptionイベントを使う場合は、次のような点に注意してください。

  • ThreadExceptionイベントとは違い、Windows Forms アプリケーションだけでなく、コンソールアプリケーションでも使えます。
  • ThreadExceptionイベントとは違い、メインスレッド以外のスレッドで例外がスローされたときも発生します。ただし、メインスレッドでスローされる例外について調べる場合は、ThreadExceptionイベントを使ったほうが良いでしょう。理由は、後述します。
  • .NET Framework 1.1以前では、メインスレッド以外のスレッドでスローされた例外によりThreadExceptionイベントが発生した場合は、アプリケーションが終了しません(UnhandledExceptionEventArgs.IsTerminatingプロパティがFalseになります)。
  • .NET Framework 1.1以前では、メインのアプリケーションドメイン(アプリケーションの起動時にシステムによって作成されたアプリケーションドメイン)のみで有効です。
  • メインスレッドで例外がスローされたときは、「アプリケーションのコンポーネントで、ハンドルされていない例外が発生しました。...」というダイアログが表示され、UnhandledExceptionイベントは発生しません。MSDN「AppDomain.UnhandledException イベント」には、「.NET Framework Version 1.0 および 1.1 では、このイベントが発生する前に、アプリケーションの終了とデバッグ オプションがユーザーに報告されます。」とありますが、その後にUnhandledExceptionイベントが発生しませんし、.NET Framework 2.0以降でも同じ症状が出ます。この問題について詳しくは、「Console apps and AppDomain.CurrentDomain.UnhandledException」をご覧ください。

Visual Studio 2005以降のVB.NETで、My.Application.UnhandledExceptionイベントを使用する方法

Visual Studio 2005以降のVB.NETでは、My.Application.UnhandledExceptionイベントを使用することができます。

My.Application.UnhandledExceptionイベントハンドラは、ApplicationEvents.vbに記述すると便利です。ApplicationEvents.vbは、プロジェクトのプロパティの「アプリケーション」タブにある「アプリケーションイベントの表示」ボタンをクリックすることにより表示できます。(プロジェクトのプロパティは、メニューの「プロジェクト」-「プロパティ」で表示できます。)

以下にApplicationEvents.vbにUnhandledExceptionイベントハンドラを記述した例を示します。

VB.NET
コードを隠すコードを選択
Namespace My

    Partial Friend Class MyApplication

        Private Sub MyApplication_UnhandledException( _
            ByVal sender As Object, _
            ByVal e As Microsoft.VisualBasic.ApplicationServices. _
                UnhandledExceptionEventArgs) _
            Handles Me.UnhandledException

            Try
                'e.ExitApplicationをTrueにすると、アプリケーションが終了する
                'デフォルトでTrueなので、必要ない
                e.ExitApplication = True

                MsgBox(e.Exception.Message, MsgBoxStyle.Critical, "エラー")
            Finally
                'アプリケーションを終了する
                Application.Exit()
            End Try
        End Sub
    End Class

End Namespace

UnhandledExceptionイベントを使用する際の注意点を以下にあげます。

  • アプリケーションフレームワークが有効になっている必要があります。デフォルトで有効になっていますが、変更するには、プロジェクトのプロパティの「アプリケーション」タブの「アプリケーションフレームワークを有効にする」を変更します。
  • 私の試した限りでは、Application.ThreadExceptionイベントとほぼ同じようです。注意点もThreadExceptionイベントで指摘した点がそのまま当てはまります。
  • 履歴:
  • 2008/11/23 「Visual Studio 2005以降のVB.NETで、My.Application.UnhandledExceptionイベントを使用する方法」でアプリケーションを終了するコードが抜けているのを修正。
  • 2010/7/22 リンク切れを修正。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。