views:

304

answers:

2

I'm seeing different behavior with exceptions being caught or not being caught when I am debugging vs. when I am running a compiled .exe. I have two forms (Form1 and Form2). Form1 has a button on it which instantiates and calls ShowDialog on Form2. Form2 has a button on it which intentionally produces a divide by zero error. When I'm debugging, the catch block in Form1 is hit. When I run the compiled .exe, it is NOT hit, and instead I get a message box that states, "Unhandled exception has occurred in your application. If you click continue, the application will ignore this error and attempt to continue. If you click Quit, the application will close immediately...Attempted to divide by zero". My question is why do you get different behavior when debugging vs. when running the .exe? If that is the expected behavior, then would it be considered necessary to put try/catch blocks in every single event handler? That seems kind of crazy over kill, doesn't it?

Here's the code for Form1.

public partial class Form1 : Form
{
    public Form1()
    {
            InitializeComponent();

    }

    private void button1_Click(object sender, EventArgs e)
    {
        try
        {
            Form2 f2 = new Form2();
            f2.ShowDialog();
        }
        catch(Exception eX)
        {
            MessageBox.Show( eX.ToString()); //This line hit when debugging only
        }
    }
}

Here's Form2's code:

public partial class Form2 : Form
{
    public Form2()
    {
            InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
            int x = 0;
            int y = 7 / x;

    }
}
+5  A: 
Mark Byers
Interestingly though if I compile in Debug mode and the run the .exe, I still do not see the message box (the catch block isn't hit). So regardless of whether I'm in Release or Debug, the behavior is the same...the exception is unhandled. But stepping through the code, you hit the catch block. I guess it makes sense that ShowDialog is not really on the same thread as where the error occurs. The fact that you hit that exception handler when debugging seems like a Visual Studio bug to me.
Steve Dunbar
Yes, it seems to be Visual Studio that is changing the behaviour, although I'm not sure it's a bug. It seems to be intentional to aid debugging. However this feature misleads you into thinking it's OK to catch exceptions from ShowDialog when in fact it seems that doesn't work.
Mark Byers
+4  A: 

Yes, this is by design and is closely associated with the way Windows Forms works. In a WF app, code runs in response to messages posted to the active window by Windows. Every native Windows app contains a message loop to detect these messages. The WF plumbing ensures one of your event handlers runs in response; button1_Click in your example code.

Most WF controls implement their own event handlers. A PictureBox for example has a Paint event handler that ensures its Image is drawn to the screen. This is all done automatically, you don't have to write any code yourself to make this work.

There is however a problem when this code throws an exception, there is no way for you to catch such an exception since none of your own code was involved. In other words, there is no place for you to inject your own try block. The very last bit of your own program's code that was involved is the code that got the message loop started. The Application.Run() method call, normally in Program.cs. Or the Form.ShowDialog() call if you display a dialog. Either of those methods start a message loop. Putting a try block around the Application.Run() call isn't useful, the app will terminate after catching an exception.

To combat this problem, the WF message loop code contains a try block around the code that dispatches an event. Its catch clause displays the dialog you mentioned, it is implemented by the ThreadExceptionDialog class.

Getting to the point of your question: this catch clause really gets in the way of troubleshooting problems with your code when you debug. The debugger will only stop on an exception when there is no catch block that handles the exception. ut when your code throws an exception, you'll want to know about it when you debug. The previously mentioned code in the message loop is aware whether or not a debugger is attached. If it is, it dispatches events without the try/catch block. Now, when your code throws an exception, there is no catch clause to handle it and the debugger will stop the program, giving you a chance to find out what went wrong.

Perhaps you see now why your program behaves the way it does. When you debug, the catch clause in the message loop is disabled, giving the catch clause in the Form1 code a chance to catch the exception. When you don't, the message loop catch clause handles the exception (by displaying the dialog) and prevents the exception from unwinding to the Form1 code.

You can prevent the message loop catch clause from being used at all by calling the Application.SetUnhandledExceptionMode() method, passing UnhandledExceptionMode.ThrowException. Do so in the Main() method, before the Application.Run() call. Now your program will behave the same either way.

This is in general not a bad idea, giving the user the option to Continue in the exception dialog is a questionable feature. Do implement an event handler for AppDomain.UnhandledException event in that case so there's at least some diagnostic to the user.

Hans Passant