予期せぬ例外エラー、プログラマにとってはもちろん、使用者にとっても怖い事象ですよね。もし、この時にデータを救済する方法があったら…、あるんです。
それが CurrentDomain_UnhandledException と、Application_ThreadException の各イベントとなります。

どちらも Program.cs に記述します。準備として、static void Main() を書き換えます。私は例外発生時にデータを保存するため、メインフォームを静的に配置しています。

static readonly MainForm form = new(); /// <summary> /// The main entry point for the application. /// </summary> [STAThread] //--------------------------------------------------------------- // メイン //--------------------------------------------------------------- static void Main() { Application.ThreadException += new(Application_ThreadException); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); AppDomain.CurrentDomain.UnhandledException += new(CurrentDomain_UnhandledException); ApplicationConfiguration.Initialize(); Application.Run(form); }
//--------------------------------------------------------------- // 捕捉されなかった例外が発生 //--------------------------------------------------------------- private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { try { EmergencyDataEvacuation( (Exception)e.ExceptionObject, "CurrentDomain UnhandledException"); } finally { Environment.Exit(1); } } private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { try { EmergencyDataEvacuation(e.Exception, "Application ThreadException"); } finally { Application.Exit(); } }

EmergencyDataEvacuation とは、自分で作成したデータ待避メソッドです。二つの例外補足から、それぞれ呼び出すようにしています。以下は例外ログを保存して表示する例です。

//--------------------------------------------------------------- // 例外発生時の処理 //--------------------------------------------------------------- public static void EmergencyDataEvacuation(Exception ex, string exception) { // 特殊フォルダパスを取得 string tempPath = Path.GetTempPath(); var assemblyName = Assembly.GetExecutingAssembly().GetName().Name; // ログからマシン名とユーザー名を削除する string machine = Environment.MachineName; string user = Environment.UserName; string exMsg = Replace(ex.Message); string stackTrace = Replace(ex.StackTrace);
// ログを出力 string pathTemp = Path.GetTempFileName(); File.Delete(pathTemp); var verAsm = Assembly.GetExecutingAssembly().GetName().Version; var verFile = System.Diagnostics.FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; string fileLog = $"{Path.GetDirectoryName(pathTemp)}\\{Path.GetFileNameWithoutExtension(pathTemp)}.{assemblyName}.log"; File.WriteAllText(fileLog, string.Empty + $"Assembly Version : {verAsm}\n" + $"File Version : {verFile}\n" + $"Exception : {exception}\n\n" + $"{ex.Message}\n\n" + $"{stackTrace}\n"); // ログを表示 string appPath = Common.FindAssociatedExecutable(".txt"); if (appPath != string.Empty) { ProcessStartInfo pInfo = new(); pInfo.FileName = appPath; pInfo.Arguments = fileLog; Process.Start(pInfo); } // マシン名とユーザー名の置換 string Replace(string message) { string msg = Regex.Replace(message, user, "{anonymus}", RegexOptions.IgnoreCase); return Regex.Replace(msg, machine, "{machine}", RegexOptions.IgnoreCase); } }
FindAssociatedExecutable は前回解説した関連付け起動のアプリパス取得メソッドです。テキストに関連付けされたアプリを使って、テンポラリフォルダに生成したログファイルを表示するようにしています。テンポラリファイル名は、名前で自分のテンポラリと判別出来るように少し改変しています。Form 終了時にテンポラリファイルを全て削除するようにしています。つまり、例外が発生し続ける限りは、テンポラリフォルダにログが蓄積する作りにしています。

ログファイルですが、ユーザー名とマシン名が記録されないように加工しています。自分の名前などをフォルダ名に入れている人も多いと思いますので、ログに名前が残るのは嫌うと思います。そこで、当該名称部分を別名に置き換える事で、ログを制作者に送ってもらえる可能性を増やすようにしています。まあ、本当に送ってもらえるかどうかは、使用者の善意に頼るしかありませんが。確認後、メーラー起動して…とは思いましたが、それはそれで嫌がられるかなあとも。

このコードでは行っていませんが、実際には静的に保存した Form 内の静的関数を呼び出して、テンポラリフォルダに編集中のデータを保存して、次回起動時にテンポラリフォルダ内を確認して、セーブデータがあれば、ユーザーに確認の後、ロードして復活させるようにしています。
例外ログ
ひとつ注意点が。例外補足中に例外が発生すると無限ループになります。そのため、例外補足中の処理では絶対に例外を発生させないよう、try で全体を括る必要があります。例外イベント内の処理全体が try finally で括ってて、finally では即座にアプリを終了させているのはそういう理由なんです。
※ 私も買おうかな。端子が後ろに出っ張るの邪魔なんですよね。