Tuesday, April 08, 2008 12:59 PM
I recently had a question on an old post regarding first chance exceptions. If you're not familiar with first chance exceptions, I suggest checking out the afore mentioned post for a quick run down, but the short story is essentially this: you can cause the debugger to trip over every exception that's thrown in your program before it is caught. This is great way to identify those sneaky exceptions that are being silently swallowed by empty try/catch(Exception e) blocks and preventing you from knowing about them. The question was whether or not there was a way to still use this feature but to ignore exceptions thrown in certain parts of the code, say from the source code of a third party component.
I've struggled with this many times myself and I'm happy to report that there is a surprisingly simple answer.
The DebuggerNonUserCode attribute can essentially make the debugger blind to certain parts of code. The attribute is actually intended to be appended to designer generated code in order to prevent you from accidentally stepping into it during a debugging session, but it also causes the debugger to turn a blind eye to any exceptions that happen in code adorned with it.
Take the following piece of code...
public class AccountsLayer
{
public Account GetAccount(int id)
{
try { return GetAccountFromDatabase(id);
}
catch (Exception e)
{
// Oh god we messed up! Whatever you do, DON'T TELL THE CALLER! return null;
}
}
}
With first chance exceptions turned on for this lovely specimen of code, the debugger would still break on whatever exception was returned from the GetAccountFromDatabase(int) call. We may be completely aware of this exception but not interested in changing the vendor code to fix it, however, seeing this exception every time we run could get quite annoying. But by adorning the code with the DebuggerNonUserCode attribute, such as in the following example, our debugger won't break every time this section of code throws an exception...
[DebuggerNonUserCode]
public class AccountsLayer
{
public Account GetAccount(int id)
{
try { return GetAccountFromDatabase(id);
}
catch (Exception e)
{
// Oh god we messed up! Whatever you do, DON'T TELL THE CALLER! return null;
}
}
}
That's it! Now we can continue on unabated with the rest of our coding session. Please keep in mind that in most cases you would be better off taking the time simply fixing the code then intentionally ignoring it as we did here. In fact, a regularly recurring exception almost always indicates a flaw with your program logic and can usually be avoided with a simply check before entering the offending code. For example, if you find yourself attempting to write to missing files often, you would almost certainly be better off enclosing the offending code with an if (File.Exists(...)) guard clause in lieu of simply catching the resulting FileNotFoundException after the fact.
However, there are cases when simpler is better. In our case we were using a third party synchronization library that would simply try and write to an on disk cache file without first determining whether or not it existed. While some cache files always existed, certain other cache files didn't exist for a specific reason and would never exist during the life of the system. In the event that a particular cache didn't exist the system would simply fail out with a FileNotFoundException. Normally this would still be tolerable, but since this was happening on a synchronization thread we would see the same FileNotFoundException every two seconds as long as we were running the program. Since we were in the process of purging this library from our system anyway, it made more sense to simply ignore it with this attribute rather than to invest the time to fix it.
If you know specifically what type of exception is being thrown, you may also wish to simply fine tune your debugger to ignore exceptions of this type (this is also explained in the afore mentioned post). Use this approach with caution, however, as you can only fine tune to the level of the type...not instance. For example, in the previous scenario we could have tuned off FileNotFoundExceptions, however, that would have caused us to potentially miss any other FileNotFoundExceptions which would have arisen from our own code as well.
As with any tip of this nature, please use this at your own risk to ensure you're not simply turning a blind eye to a deeper problem.