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

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

ここでは、Windowsフォームアプリケーションとコンソールアプリケーションにおいて、Try...Catch...で捕捉(キャッチ、ハンドル、トラップ)されなかった例外(エラー)がスローされたときに、その例外の情報を知るための方法を紹介します。

ただし、できるだけエラー処理はTry...Catch...(詳しくは、「エラー処理(例外処理)の基本」)を使って行うべきであり、ここで紹介している方法はどうしてもそうしなければならない時のみ使用してください。

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

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

ThreadExceptionイベントが発生するのは、Windowsフォームが作成、所有しているスレッド(UIスレッド)で例外がスローされた時だけです。例えば、Thread.Startメソッドや、デリゲートのBeginInvokeメソッドなどで開始されたスレッドで発生した例外では発生しません。

ThreadExceptionイベントハンドラが呼び出された時に何もしないと、アプリケーションは続行され、例外は無視されます。例外を無視したままアプリケーションを続行させるのは危険ですので、通常はThreadExceptionイベントハンドラにアプリケーションを終了させるコードを記述します。

以下に例を示します。この例ではエントリポイントへの記述がありますが、エントリポイントが分からないという場合は、「アプリケーションのエントリポイントを自作する」をご覧ください。

VB.NET
コードを隠すコードを選択
'Imports System.Windows.Forms

''' <summary>
''' アプリケーションのメイン エントリ ポイントです。
''' </summary>
<STAThread> _
Public Shared Sub Main()
    'ThreadExceptionイベントハンドラを追加
    AddHandler Application.ThreadException, _
        AddressOf Application_ThreadException

    Application.Run(New Form1())
End Sub

'ThreadExceptionイベントハンドラ
Private Shared Sub Application_ThreadException(sender As Object, _
        e As System.Threading.ThreadExceptionEventArgs)
    Try
        'エラーメッセージを表示する
        MessageBox.Show(e.Exception.Message, "エラー")
    Finally
        'アプリケーションを終了する
        Application.Exit()
    End Try
End Sub
C#
コードを隠すコードを選択
//using System.Windows.Forms;

/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
private static void Main()
{
    //ThreadExceptionイベントハンドラを追加
    Application.ThreadException +=
        new System.Threading.ThreadExceptionEventHandler(
            Application_ThreadException);

    Application.Run(new Form1());
}

//ThreadExceptionイベントハンドラ
private static void Application_ThreadException(object sender,
    System.Threading.ThreadExceptionEventArgs e)
{
    try
    {
        //エラーメッセージを表示する
        MessageBox.Show(e.Exception.Message, "エラー");
    }
    finally
    {
        //アプリケーションを終了する
        Application.Exit();
    }
}
補足:ThreadExceptionイベントハンドラでは、通常、次のような処理を行います。
  • ユーザーにエラーが発生したことを伝える。
  • 必要ならば、例外情報をログに保存する。
  • 可能ならば、アプリケーション終了時の処理(設定の保存など)を行う。
  • アプリケーションを終了する。

このコードが書かれた状態で、例えばForm1クラスで以下のように例外をスローすると、メッセージボックスにメッセージ(「エラーのテストです。」)が表示されて、アプリケーションが終了します。

VB.NET
コードを隠すコードを選択
'Button1のClickイベントハンドラ
Private Sub Button1_Click(sender As Object, e As EventArgs) _
        Handles Button1.Click
    Throw New Exception("エラーのテストです。")
End Sub
C#
コードを隠すコードを選択
//Button1のClickイベントハンドラ
private void Button1_Click(object sender, EventArgs e)
{
    throw new Exception("エラーのテストです。");
}

常にThreadExceptionイベントが発生されるようにする

デフォルトでは、ThreadExceptionイベントが発生するかどうかはアプリケーション構成ファイルの設定によります。アプリケーション構成ファイルの設定に関係なく、常にThreadExceptionイベントが発生されるようにするには、Application.SetUnhandledExceptionModeメソッドUnhandledExceptionMode.CatchExceptionを指定します。逆に、常にThreadExceptionイベントが発生しないようにするには、UnhandledExceptionMode.ThrowExceptionを指定します。

SetUnhandledExceptionModeメソッドは、Application.Runの前に呼び出す必要があります。

VB.NET
コードを隠すコードを選択
'Imports System.Windows.Forms

''' <summary>
''' アプリケーションのメイン エントリ ポイントです。
''' </summary>
<STAThread> _
Public Shared Sub Main()
    'ThreadExceptionイベントハンドラを追加
    AddHandler Application.ThreadException, _
        AddressOf Application_ThreadException
    '常にThreadExceptionが発生されるようにする
    Application.SetUnhandledExceptionMode( _
        UnhandledExceptionMode.CatchException)

    Application.Run(New Form1())
End Sub

'ThreadExceptionイベントハンドラ
Private Shared Sub Application_ThreadException(sender As Object, _
        e As System.Threading.ThreadExceptionEventArgs)
    Try
        'エラーメッセージを表示する
        MessageBox.Show(e.Exception.Message, "エラー")
    Finally
        'アプリケーションを終了する
        Application.Exit()
    End Try
End Sub
C#
コードを隠すコードを選択
//using System.Windows.Forms;

/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
private static void Main()
{
    //ThreadExceptionイベントハンドラを追加
    Application.ThreadException +=
        new System.Threading.ThreadExceptionEventHandler(
            Application_ThreadException);
    //常にThreadExceptionが発生されるようにする
    Application.SetUnhandledExceptionMode(
        UnhandledExceptionMode.CatchException);

    Application.Run(new Form1());
}

//ThreadExceptionイベントハンドラ
private static void Application_ThreadException(object sender,
    System.Threading.ThreadExceptionEventArgs e)
{
    try
    {
        //エラーメッセージを表示する
        MessageBox.Show(e.Exception.Message, "エラー");
    }
    finally
    {
        //アプリケーションを終了する
        Application.Exit();
    }
}
補足:SetUnhandledExceptionModeメソッドは、2番目のパラメータにFalseを指定することにより、アプリケーション例外モードとすることができるようです(デフォルトは、スレッド例外モード)。しかし現在は、SetUnhandledExceptionModeメソッドの2番目のパラメータをFalseにして呼び出すと、例外がスローされます。

その他の注意点

上記以外の、ThreadExceptionイベントの注意点を以下に列挙します。

  • Visual Studioの「デバッグ開始」で実行した時は、ThreadExceptionイベントが発生せずに、デバッガのダイアログが表示されます(Visual Studioのバージョンによっては、ThreadExceptionイベントが発生することもあるようです)。デバッガダイアログを表示せずにThreadExceptionイベントを確認するには、メニューの「デバッグ」-「デバッグなしで開始」で実行します。
  • サポート技術情報 KB915322によると、.NET Framework 1.1 SP1でApplication.ThreadExceptionイベントが発生しないケースがあるようです。

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

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

UnhandledExceptionイベントはThreadExceptionイベントとは違い、UIスレッド以外のスレッドで例外がスローされた場合でも発生します。また、Windowsフォームアプリケーションだけでなく、コンソールアプリケーションでも使用できます。

さらにThreadExceptionイベントとは違い、UnhandledExceptionイベントハンドラが呼び出された後も例外が無視されるということはなく、そのままにすると通常は「(アプリケーション)は動作を停止しました。」のようなダイアログが表示され、アプリケーションは終了します。

以下にUnhandledExceptionイベントを使用した例を示します。この例はコンソールアプリケーションです。

VB.NET
コードを隠すコードを選択
Module Module1
    'エントリポイント
    Sub Main(args As String())
        'UnhandledExceptionイベントハンドラを追加する
        AddHandler System.AppDomain.CurrentDomain.UnhandledException, _
            AddressOf CurrentDomain_UnhandledException
        'または、次のようにもできる
        'Dim curDom As System.AppDomain = System.Threading.Thread.GetDomain()
        'AddHandler curDom.UnhandledException, _
        '    AddressOf CurrentDomain_UnhandledException

        '例外をスローする
        Throw New Exception("エラーのテストです。")

        Console.ReadLine()
    End Sub

    'UnhandledExceptionイベントハンドラ
    Sub CurrentDomain_UnhandledException(sender As Object, _
                                         e As UnhandledExceptionEventArgs)
        Try
            Dim ex As Exception = DirectCast(e.ExceptionObject, Exception)
            'エラーメッセージを表示する
            Console.WriteLine("エラー: {0}", ex.Message)
        Finally
            'アプリケーションを終了する
            Environment.Exit(1)
        End Try
    End Sub
End Module
C#
コードを隠すコードを選択
public class Program
{
    //エントリポイント
    static void Main(string[] args)
    {
        //UnhandledExceptionイベントハンドラを追加する
        System.AppDomain.CurrentDomain.UnhandledException +=
            new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        //または、次のようにもできる
        //System.AppDomain curDom = System.Threading.Thread.GetDomain();
        //curDom.UnhandledException +=
        //    new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

        //例外をスローする
        throw new Exception("エラーのテストです。");

        Console.ReadLine();
    }

    //UnhandledExceptionイベントハンドラ
    static void CurrentDomain_UnhandledException(object sender,
        UnhandledExceptionEventArgs e)
    {
        try
        {
            Exception ex = (Exception)e.ExceptionObject;
            //エラーメッセージを表示する
            Console.WriteLine("エラー: {0}", ex.Message);
        }
        finally
        {
            //アプリケーションを終了する
            Environment.Exit(1);
        }
    }
}
補足:UnhandledExceptionイベントハンドラでアプリケーションを終了させる時、Environment.ExitではなくApplication.Exitメソッドを使うと、「(アプリケーション)は動作を停止しました。」というダイアログが表示されてしまいます。

UIスレッドの例外をUnhandledExceptionイベントで捕捉する

Windowsフォームアプリケーションで捕捉されなかったすべての例外を調べるには、基本的には、ThreadExceptionイベントとUnhandledExceptionイベントの両方を使います。UIスレッドで例外がスローされた時はThreadExceptionイベントが発生し、それ以外ではUnhandledExceptionイベントが発生します。

もしUnhandledExceptionイベントをUIスレッドの例外でも発生させたいのであれば、SetUnhandledExceptionModeメソッドにThrowExceptionを指定して呼び出します。

VB.NET
コードを隠すコードを選択
'Imports System.Windows.Forms

''' <summary>
''' アプリケーションのメイン エントリ ポイントです。
''' </summary>
<STAThread> _
Public Shared Sub Main()
    'ThreadExceptionイベントハンドラを追加
    AddHandler Application.ThreadException, _
        AddressOf Application_ThreadException
    'ThreadExceptionが発生しないようにする
    Application.SetUnhandledExceptionMode( _
        UnhandledExceptionMode.ThrowException)

    'UnhandledExceptionイベントハンドラを追加
    AddHandler System.AppDomain.CurrentDomain.UnhandledException, _
        AddressOf CurrentDomain_UnhandledException

    Application.Run(New Form1())
End Sub

'UnhandledExceptionイベントハンドラ
Private Shared Sub CurrentDomain_UnhandledException(sender As Object, _
        e As UnhandledExceptionEventArgs)
    Try
        'エラーメッセージを表示する
        MessageBox.Show(DirectCast(e.ExceptionObject, Exception).Message, _
                        "エラー")
    Finally
        'アプリケーションを終了する
        Environment.Exit(1)
    End Try
End Sub

'ThreadExceptionイベントハンドラ
Private Shared Sub Application_ThreadException(sender As Object, _
        e As System.Threading.ThreadExceptionEventArgs)
    Try
        'エラーメッセージを表示する
        MessageBox.Show(e.Exception.Message, "エラー")
    Finally
        'アプリケーションを終了する
        Application.Exit()
    End Try
End Sub
C#
コードを隠すコードを選択
//using System.Windows.Forms;

/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
private static void Main()
{
    //ThreadExceptionイベントハンドラを追加
    Application.ThreadException +=
        new System.Threading.ThreadExceptionEventHandler(
            Application_ThreadException);
    //ThreadExceptionが発生しないようにする
    Application.SetUnhandledExceptionMode(
        UnhandledExceptionMode.ThrowException);

    //UnhandledExceptionイベントハンドラを追加
    System.AppDomain.CurrentDomain.UnhandledException +=
        new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

    Application.Run(new Form1());
}

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

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

その他の注意点

上記以外のUnhandledExceptionイベントの注意点を以下に列挙します。

  • .NET Framework 1.1以前では、メインのアプリケーションドメイン(アプリケーションの起動時にシステムによって作成されたアプリケーションドメイン)のみで有効です。
  • .NET Framework 1.1以前では、メインアプリケーションスレッド以外のスレッドで例外がスローされた場合、UnhandledExceptionイベントハンドラのUnhandledExceptionEventArgs.IsTerminatingプロパティがFalseになり、アプリケーションが終了しません。詳しくは、MSDNの「AppDomain.UnhandledException イベント」や「UnhandledExceptionEventArgs.IsTerminating プロパティ」をご覧ください。
  • .NET Framework 1.1以前では、UnhandledExceptionイベントが発生する前に「アプリケーションのコンポーネントで、ハンドルされていない例外が発生しました。...」というダイアログが表示されてしまいます。この問題について詳しくは、「Console apps and AppDomain.CurrentDomain.UnhandledException」をご覧ください。

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

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

Application.UnhandledExceptionイベントを使用するには、アプリケーションフレームワークが有効になっている必要があります。デフォルトで有効になっていますが、無効になっている場合は、プロジェクトのプロパティの「アプリケーション」タブの「アプリケーションフレームワークを有効にする」にチェックを入れます。

Application.UnhandledExceptionイベントは、Application.ThreadExceptionイベントとほぼ同じと考えて良さそうです。ただし、イベントハンドラでUnhandledExceptionEventArgs.ExitApplicationプロパティをTrueにすることでアプリケーションを終了させることができるという機能が加わっています。

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

捕捉できない例外

これらのイベントを使っても例外を捕捉できないケースがあります。そのようなケースについて、知っている限り紹介します。

  • 履歴:
  • 2008/11/23 「Visual Studio 2005以降のVB.NETで、My.Application.UnhandledExceptionイベントを使用する方法」でアプリケーションを終了するコードが抜けているのを修正。
  • 2010/7/22 リンク切れを修正。
  • 2013/6/17 エントリポイントでイベントハンドラを追加するようにコードを変更。「捕捉できない例外」を追加。それ以外の説明も書き直す。

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

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。