キャッチしきれなかった例外を拾う

ソースコードの中に try ... catch をちりばめて、なるべく堅牢なアプリケーションを作るというのは言葉でいうのはたやすいですが、実際は、なかなかすべてに手がまわるものではありません。

そこで、キャッチしきれなかった例外を拾うという方法があります。

Program.cs に Application.ThreadException にログを記録したりメッセージボックスを表示するハンドラを登録します。AppDomain.CurrentDomain.UnhandledException も同様にログを記録したりメッセージボックスを表示するハンドラを登録してください

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

namespace aaa
{

	static class Program
	{

		[STAThread]

		static void Main()
		{

			// UI スレッドの未処理例外を捕捉する設定
			Application.SetUnhandledExceptionMode( UnhandledExceptionMode.CatchException ); // 動かしつづけたいとき.	
			//Application.SetUnhandledExceptionMode( UnhandledExceptionMode.ThrowException ); // とりあえず終了させたいとき.

			// UI スレッドの未処理例外を処理するイベントハンドラを登録する.
			Application.ThreadException += Application_ThreadException;

			// UI スレッド以外の未処理例外を処理するイベントハンドラを登録する.
			AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault( false );

			Form1 f = new Form1();
			Application.Run( f );

		}

		private static void Application_ThreadException( object sender, ThreadExceptionEventArgs e )
		{
			try
			{
				string cap = "Application_ThreadException";
				MessageBox.Show( e.Exception.ToString(), cap );
			}
			catch
			{
				// NOP.
				// メッセージ表示処理の例外はここでは再送出しない
			}
		}

		private static void CurrentDomain_UnhandledException( object sender, UnhandledExceptionEventArgs e )
		{
			try
			{
				string cap = "CurrentDomain_UnhandledException";
				Exception excp = ( Exception )( e.ExceptionObject );
				MessageBox.Show( $"{excp}\r\n\r\nIsTerminating={e.IsTerminating}", cap );
			}
			catch
			{
				// NOP.
				// メッセージ表示処理の例外はここでは再送出しない
			}
		}
	}
}

これを書いておいたうえで Form1.cs に、button1, button2, button3, button4 を配置しクリックイベントを準備しておきます。そのうえでこのコードを書くと、その効き目が分かると思います。

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

namespace aaa
{
	public partial class Form1 : Form
	{

		// わざとエラーを出すコード.
		private void func_error_occur()
		{

			int w = 640;
			int h = 480;
			int numpix = w * h;
			byte[] data = new byte[numpix];

			int counter = 0;

			const int ADD = 100;

			for ( int j = 0; j < ( h + ADD ); j++ )
			{
				for ( int i = 0; i < ( w + ADD ); i++ )
				{
					data[i + w * j] = (byte)( counter );
					counter++;
				}
			}

		}

		// スレッドワーカでわざとエラーを出すコード.
		private void worker_func_error_occur()
		{

			int w = 640;
			int h = 480;
			int numpix = w * h;
			byte[] data = new byte[numpix];

			int counter = 0;

			const int ADD = 100;

			for ( int j = 0; j < ( h + ADD ); j++ )
			{
				for ( int i = 0; i < ( w + ADD ); i++ )
				{
					data[i + w * j] = (byte)( counter );
					counter++;
				}
			}

		}

		// スレッドワーカでわざとエラーを出すコード、try...catch つき.
		private void worker_func_error_occur_with_try_catch()
		{

			int w = 640;
			int h = 480;
			int numpix = w * h;
			byte[] data = new byte[numpix];

			int counter = 0;

			const int ADD = 100;

			try
			{
				for ( int j = 0; j < ( h + ADD ); j++ )
				{
					for ( int i = 0; i < ( w + ADD ); i++ )
					{
						data[i + w * j] = (byte)( counter );
						counter++;
					}
				}
			}
			catch ( Exception excp )
			{
				MessageBox.Show( excp.ToString() );
			}

		}

		//-------------------------------------------------------------

		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load( object sender, EventArgs e )
		{
			button1.Text = "例外キャッチコードあり";
			button2.Text = "例外キャッチコードなし";
			button3.Text = "スレッドコード内で例外キャッチコードあり";
			button4.Text = "スレッドコード内で例外キャッチコードなし";
		}

		private void button1_Click( object sender, EventArgs e )
		{
			try
			{
				func_error_occur();
			}
			catch ( Exception excp )
			{
				MessageBox.Show( excp.ToString() );
			}
		}

		private void button2_Click( object sender, EventArgs e )
		{
			func_error_occur();
		}

		private void button3_Click( object sender, EventArgs e )
		{
			Thread the_thread = new Thread( worker_func_error_occur_with_try_catch );
			the_thread.Start();
		}

		private void button4_Click( object sender, EventArgs e )
		{
			Thread the_thread = new Thread( worker_func_error_occur );
			the_thread.Start();
		}
	}
}

さかのぼるスタックトレース情報が多すぎて情報過多である場合は、下記のような独自メソッドを書いて調整してください。

max_stack_frame_count を 1 にするとエラー発生行の情報のみ得られます。

max_stack_frame_count を 3 にすると、呼び出し3層までの情報が得られます。

max_stack_frame_count を int.MaxValue にすると、すべてのスタックトレース層の情報が得られます。

		private bool MyGetExceptionMessage( ref string str, Exception excp, int max_stack_frame_count )
		{

			str = "";

			if ( excp == null )
			{
				return false;
			}

			if ( max_stack_frame_count <= 0 )
			{
				return false;
			}

			StackTrace stack_trace = new StackTrace( excp, true );
			StackFrame[] frames = stack_trace.GetFrames();

			if ( frames == null )
			{
				return false;
			}

			int valid_frame_count = 0;

			for ( int i = 0; i < frames.Length; i++ )
			{
				string file_path = frames[i].GetFileName();
				int line_number = frames[i].GetFileLineNumber();

				if ( file_path != null )
				{
					if ( file_path != "" )
					{
						string file_name = Path.GetFileName( file_path );

						if ( valid_frame_count > 0 )
						{
							str += "\r\n";
						}

						str += $"{file_name} : {line_number} 行目";

						valid_frame_count++;

						if ( valid_frame_count >= max_stack_frame_count )
						{
							break;
						}
					}
				}
			}

			if ( valid_frame_count == 0 )
			{
				return false;
			}

			return true;

		}