Let’s start with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using System; using System.Windows.Forms; class Program1 { [STAThread] static void Main() { var form = new Form(); form.Load += (sender, e) => throw new Exception(); try { Application.Run(form); } catch(Exception e) { MessageBox.Show("This doesn't execute"); } finally { MessageBox.Show("But this shows up"); } } } |
You throw exception as soon as your form is loaded, however, catch block is not executed. What happens?
Initially I though this is out of band exception which is thrown on another thread. But after some debugging the reason is much simpler. WinForms provide special event handler for unhandled exceptions, namely ThreadException. If you decompile some code, you can see this:
System.Windows.Forms.NativeWindow.Callback
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
private IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) { Message message = Message.Create(hWnd, msg, wparam, lparam); try { if (this.weakThisPtr.IsAlive && this.weakThisPtr.Target != null) { this.WndProc(ref message); } else { this.DefWndProc(ref message); } } catch (Exception e) { this.OnThreadException(e); } finally { if (msg == 130) { this.ReleaseHandle(false); } if (msg == NativeMethods.WM_UIUNSUBCLASS) { this.ReleaseHandle(true); } } return message.Result; } |
When you throw exception, it is being caught by WinForms and passed to the event handler. There we can see this:
System.Windows.Forms.Application.ThreadContext.OnThreadException
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
ThreadExceptionDialog threadExceptionDialog = new ThreadExceptionDialog(t); DialogResult dialogResult = DialogResult.OK; IntSecurity.ModifyFocus.Assert(); try { dialogResult = threadExceptionDialog.ShowDialog(); } finally { CodeAccessPermission.RevertAssert(); threadExceptionDialog.Dispose(); } if (dialogResult != DialogResult.Abort) { if (dialogResult == DialogResult.Yes) { WarningException ex = t as WarningException; if (ex != null) { Help.ShowHelp(null, ex.HelpUrl, ex.HelpTopic); } } } else { Application.ExitInternal(); new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); Environment.Exit(0); } |
The application is closed. Top level catch block is not executed because there is physically no exception anymore. Finally is being executed because of stack unwinding. If there was call to Environment.FailFast
, it would not be executed.