連載
» 2005年07月01日 05時00分 UPDATE

.NET TIPS:適切に処理されなかった例外をキャッチするには?

[一色政彦,デジタルアドバンテージ]
「.NET TIPS」のインデックス

連載目次

 Windowsフォーム・アプリケーションやコンソール・アプリケーションを実装する際、例外が発生する可能性がある個所では、基本的に、Try-Catch構文によりその例外をキャッチして適切な処置を施す必要がある。しかし現実には、例外が正しくキャッチ(=トラップ)されていないというケースは多々あり、その場合にはアプリケーションの実行中に次のような.NET Framework標準のエラー・ダイアログが表示されてしまうことになる。

例外が処理されていない場合に表示されるエラー・ダイアログの例 例外が処理されていない場合に表示されるエラー・ダイアログの例

 このエラー・ダイアログは、見て分かるとおり、一般的なユーザーにとって分かりやすいものとはいえない。パソコンの操作に自信のない人であれば、これを見た途端に困惑してしまうということもあるだろう。

 このような事態を避けるために、.NET標準のエラー・ダイアログを、もっとユーザー・フレンドリな自作のエラー・ダイアログに切り替えたいという要望も、きっと多いのではないだろうか。

 そこで本稿では、Windowsアプリケーションやコンソール・アプリケーションで処理されていない例外を、1カ所でまとめてハンドルしてしまう方法を紹介する。この方法によりハンドルしたメソッド内で、独自に作成したダイアログを表示するようにすれば、前述の要望も実現可能である。

処理されていない例外をハンドルするには?

 処理されていない例外をハンドルするには、以下の2つの方法が用意されている。

1. Application.ThreadExceptionイベントの活用

 処理されなかった例外をハンドルするには、WindowsフォームならApplicationクラス(System.Windows.Forms名前空間)のThreadExceptionイベントをハンドルして処理すればよい。このイベントは、Windowsフォーム・アプリケーションのメイン・スレッド(=ApplicationクラスのRunメソッドにより実行されるアプリケーションのコンテキスト)内で発生した未処理の例外をハンドルするためのものである。

2. Thread.GetDomain().UnhandledExceptionイベントの活用

 それでは、上記のメイン・スレッド以外のコンテキスト上で発生した例外は、どのようにしてハンドルすればよいのだろうか? これを行うのが、現在のアプリケーション・ドメイン(=AppDomainクラス(System名前空間)のオブジェクトにより表され、Thread.GetDomainメソッドにより取得できる)のUnhandledExceptionイベントである。

 例えば、マルチスレッド・アプリケーションなどにおいて、メイン・スレッド以外のスレッドで発生した例外や、コンソール・アプリケーションで発生した例外などは、すべてこのUnhandledExceptionイベントをハンドルして処理すればよい。

 以上、これら2つのイベントを活用して未処理の例外を実際に処理しているのが、次のサンプル・プログラムだ。

using System;
using System.Windows.Forms;
using System.Threading;

namespace WindowsApplication1
{
  public class Program
  {
    [STAThread]
    static void Main()
    {
      // ThreadExceptionイベント・ハンドラを登録する
      Application.ThreadException += new
        ThreadExceptionEventHandler(Application_ThreadException);

      // UnhandledExceptionイベント・ハンドラを登録する
      Thread.GetDomain().UnhandledException += new
        UnhandledExceptionEventHandler(Application_UnhandledException);

      // メイン・スレッド以外の例外はUnhandledExceptionでハンドル
      //string buffer = "1"; char error = buffer[2];

      // ここで実行されるメイン・アプリケーション・スレッドの例外は
      // Application_ThreadExceptionでハンドルできる
      Application.Run(new Form1());
    }

    // 未処理例外をキャッチするイベント・ハンドラ
    // (Windowsアプリケーション用)
    public static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
      ShowErrorMessage(e.Exception, "Application_ThreadExceptionによる例外通知です。");
    }

    // 未処理例外をキャッチするイベント・ハンドラ
    // (主にコンソール・アプリケーション用)
    public static void Application_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
      Exception ex = e.ExceptionObject as Exception;
      if (ex != null)
      {
        ShowErrorMessage(ex, "Application_UnhandledExceptionによる例外通知です。");
      }
    }

    // ユーザー・フレンドリなダイアログを表示するメソッド
    public static void ShowErrorMessage(Exception ex, string extraMessage)
    {
      MessageBox.Show(extraMessage + " \n――――――――\n\n" +
        "エラーが発生しました。開発元にお知らせください\n\n" +
        "【エラー内容】\n" + ex.Message + "\n\n" +
        "【スタックトレース】\n" + ex.StackTrace);
    }
  }
}

処理されていない例外をハンドルするサンプル・プログラム(C#:program.cs)
サンプル・プログラム(program.cs)のダウンロード
Windowsフォーム(form1.cs)のダウンロード

Imports System.Threading

Public Class Program

  <STAThread()> _
  Shared Sub Main()
    ' ThreadExceptionイベント・ハンドラを登録する
    AddHandler Application.ThreadException, AddressOf Application_ThreadException

    ' UnhandledExceptionイベント・ハンドラを登録する
    AddHandler Thread.GetDomain().UnhandledException, AddressOf Application_UnhandledException

    ' メイン・スレッド以外の例外はUnhandledExceptionでハンドル
    'Dim buffer As String = "1"
    'Dim [error] As Char = buffer.Chars(2)

    ' ここで実行されるメイン・アプリケーション・スレッドの例外は
    ' Application_ThreadExceptionでハンドルできる
    Application.Run(New Form1)
  End Sub

  ' 未処理例外をキャッチするイベント・ハンドラ
  ' (Windowsアプリケーション用)
  Public Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As ThreadExceptionEventArgs)
    ShowErrorMessage(e.Exception, "Application.ThreadExceptionによる例外通知です。")
  End Sub

  ' 未処理例外をキャッチするイベント・ハンドラ
  ' (主にコンソール・アプリケーション用)
  Public Shared Sub Application_UnhandledException(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
    Dim ex As Exception = CType(e.ExceptionObject, Exception)
    If Not ex Is Nothing Then
      ShowErrorMessage(ex, "Application.UnhandledExceptionによる例外通知です。")
    End If
  End Sub

  ' ユーザー・フレンドリなダイアログを表示するメソッド
  Public Shared Sub ShowErrorMessage(ByVal ex As Exception, ByVal extraMessage As String)
    MessageBox.Show(extraMessage & vbLf & "――――――――" & _
     vbLf & vbLf & _
     "エラーが発生しました。開発元にお知らせください" & _
     vbLf & vbLf & _
     "【エラー内容】" & vbLf & ex.Message & vbLf & vbLf & _
     "【スタックトレース】" & vbLf & ex.StackTrace)
  End Sub

End Class

処理されていない例外をハンドルするサンプル・プログラム(VB.NET:program.vb)
このプログラムを実行するには、アプリケーションのエントリポイントを、「ProgramクラスのMainメソッド」に切り替える必要がある。具体的には、Visual Studio .NETの[ソリューション エクスプローラ]でプロジェクト項目を右クリックしてコンテキスト・メニューを表示し、そのメニューから[プロパティ]を選択して[<プロジェクトの> プロパティ ページ]ダイアログを開き、そのダイアログの[共通プロパティ]−[全般]の[スタートアップの設定]の値を「Sub Main」に設定し直せばよい。

サンプル・プログラム(program.vb)のダウンロード
Windowsフォーム(form1.vb)のダウンロード

 このサンプル・プログラムを実行したのが次の画面である。

Application.ThreadExceptionイベントによりハンドルした例外 Application.ThreadExceptionイベントによりハンドルした例外

Thread.GetDomain().UnhandledExceptionイベントによりハンドルした例外 Thread.GetDomain().UnhandledExceptionイベントによりハンドルした例外

 ここで作成しているダイアログはあまり良い例とはいえないが、より使いやすく、見やすく作り込むことで、ユーザビリティの高いエラー通知ダイアログにすることができるだろう。

 なおVisual Studio .NETがインストールされている環境などでは、その設定内容によっては、上記に示した例外をハンドルするためのイベントが発生する前に、デバッガを選択するための[Just-In-Time デバッグ]ダイアログが表示されることがある。このダイアログが表示されるのを抑止する方法については、「TIPS:[アプリケーション・エラー]ダイアログを非表示にするには?」を参照してほしい。

カテゴリ:Windowsフォーム 処理対象:例外
カテゴリ:コンソール・アプリケーション 処理対象:例外
使用ライブラリ:Applicationクラス(System.Windows.Forms名前空間)
使用ライブラリ:AppDomainクラス(System名前空間)
関連TIPS:[アプリケーション・エラー]ダイアログを非表示にするには?


「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。