I previously mentioned that catch / rethrow an exception impedes debuggability because the callstack is unwound at the catch block, and thus you lose the callstack in the debugger.  On technique to fix this is to take advantage of Just-My-Code ,which was added in .Net 2.0 (whidbey).  JMC lets you mark each function as "my code" (user) or "not my code" (non-user).   The debugger can then filter out code you don't care about.  I blogged earlier about how the debugger can apply this to stepping, but it can also apply this to exceptions.

Breaking into the debugger for exceptions:
Previously, the debugger had 2 events to stop at with exceptions:
1. First chance - break when an exception is thrown. Since exceptions may get thrown and caught a lot, this has a tendency to produce a lot of noise. You may stop on some random exception in a 3rd-party library that you don't care about.
2. Unhandled (2nd chance) -break when an exception goes unhandled. This is bad because the exception may be caught + rethrown several times and by the time the exception is finally unhandled, the original stack may no longer be available in the debugger (although it may be viewable by Exception.StackTrace property).

As noted, both of those options have serious problems. JMC lets a debugger apply more practical events:
3. User First Chance: break when an exception first enters user-code. Thus exceptions caught and thrown by a  non-user library are then filtered out. You only get notified about exceptions entering the code you care about.
4. User Unhandled: break when an exception is thrown from user code, but about to be caught in non-user code. Specifically, this stops at a Catch-Handler-Found notification in non-user code, which comes before the catcher is actually executed and thus before the stack is unwound.  

User-unhandled is especially useful because it doesn't have the noise of 1st-chance and stops before the stack is unwound. User-Unhandled is not a debug event at the ICorDebug level. It's a "virtual debug event" that Visual Studio builds on simpler debug events (Catch-Handler-Found).

An example bringing it together:
By marking Catch/rethrow blocks as non-user code, you can cause the debugger to raise User-Unhandled events, and thus break in the debugger before you lose the stack.

You can mark code as non-user code via the System.Diagnostics.DebuggerNonUserCodeAttribute.


using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

class Program
{
    // This is user-code
    static void Main(string[] args)
    {
        WrapperToNotifyDebugger();
    }

    [DebuggerNonUserCode()] // attribute makes this non-user code
    static void WrapperToNotifyDebugger()
    {
        try
        {
            ButtonClickHandler();
        }
        catch // once we execute the catch, the stack of the original throw is already unwound.
        {
            throw; 
        }
    }

    // This is user-code.
    static void ButtonClickHandler()
    {
        throw new ApplicationException("Here's an exception. We want to see the callstack");
    }

}

So if you run the following snippet with JMC disabled (in VS, see Tools | Options | Debugger | Enable Just My Code), you'll stop in the debugger at an unhandled notification at the rethrow:

> random.exe!Program.WrapperToNotifyDebugger() C#
  random.exe!Program.Main(string[] args = {Dimensions:[0]}) C#

Notice at this point, the original throw site (ButtonClickHandler) is not on the stack.

However, if you enable JMC, you stop in the debugger at the original throw site:

> random.exe!Program.ButtonClickHandler() C#
  random.exe!Program.WrapperToNotifyDebugger() C#
  random.exe!Program.Main(string[] args = {Dimensions:[0]}) C#
  

and the debugger says "ApplicationException was unhandled by user code".
 

This technique can be a life saver in making catch-rethrow sites debuggable.