Monday, 19 May 2008

DebuggerNonUserCode: Suppressing ignorable exceptions in the debugger

Have you ever written code that made you want to wash your hands (or your keyboard!) the minute you'd finished typing? So dirty it was, that you surreptitiously looked around to see if anyone else had noticed the smell emanating from your instance of Visual Studio. We don't like it, but sometimes it's unavoidable. The clock is ticking; Google mentions no alternatives, so you put a peg on your nose, and plunge on.

It doesn't happen often in the clean, fresh-smelling environment of C#; usually it's when I'm writing VBA code that I feel like putting on the rubber gloves. One case that I do encounter in .Net more often than I would like though, is when Exceptions are thrown in non-exceptional cases, and I have to catch those exceptions to determine what to next.

Take Enum.Parse as an example. It converts a string value to an enum value. More likely than not, the string value will have been entered by the user, so there's a good chance that it's not valid. If it can't be parsed, then an ArgumentException is thrown. So if you want to trap for invalid values, you're forced to do this:

enum MyEnum
{
    Value1,
    Value2,
}

bool TryParse(string value, out MyEnum convertedValue)
{
    try
    {
        convertedValue = (MyEnum)Enum.Parse(typeof(MyEnum), value);
        return true;
    }
    catch (ArgumentException)
    {
        convertedValue = 0;
        return false;
    }
}

Apart from looking nasty, there's another problem that you only notice when you start using the debugger. If you've set the debugger to break whenever an exception is thrown - as I usually do to make sure there are no problems sneaking through overly generic catch handlers - it will break in this method whenever there's invalid input, even though you have the catch statement there and it wouldn't really crash your app. You could configure the debugger to ignore ArgumentException - but then you'll likely miss cases that you really do want to know about.

As of .Net 2.0, there's an easy solution to this problem: DebuggerNonUserCode. This is an attribute that you put against a method to tell the debugger "Nothing to do with me guv'. Ain't my code!". The gullible debugger will believe you, and won't break in that method: using the attribute makes the debugger skip the method altogether, even when you're stepping through code; exceptions that occur, and are then caught within the method won't break into the debugger. It will treat it as if it were a call to a Framework assembly, and should an exception go unhandled, it will be reported one level up the call stack, in the code that called the method. All this is part of the Just-My-Code feature, which Mike Stall gives the low-down on here.

Obviously this is powerful stuff. Make sure you've tested your code well before you apply the attribute -  or you won't be able to debug where it's going wrong (actually there is a way to do it - see the note below). For this reason, I'd suggest using it on the smallest method you can. Also make sure that you are catching very specific exceptions - notice that I'm using ArgumentException in this case, rather than a catch-all Exception.

I've found this especially useful when programming Excel. In the Excel object model, collections give you no API for discovering  if particular members exist. The only way to find out is to make the call, and see if you get an exception. Here's an example of an extension method that checks whether a Worksheet exists, decorated with the DebuggerNonuserCode attribute:

using System.Diagnostics;
using XL = Microsoft.Office.Interop.Excel;

public static class WorkbookExtensions 
{
    [DebuggerNonUserCode]
    public static bool TryGetWorksheet(this XL.Workbook wb, string worksheetName, out XL.Worksheet retrievedWorksheet)
    {
        bool exists = false;
        retrievedWorksheet = null;

        try
        {
            retrievedWorksheet = GetWorksheet(wb, worksheetName);
            exists = retrievedWorksheet != null;
        }
        catch(COMException)
        {
            exists = false;
        }

        return exists;
    }

    [DebuggerNonUserCode]
    public static XL.Worksheet GetWorksheet(this XL.Workbook wb, string worksheetName)
    {
        return wb.Worksheets.get_Item(worksheetName) as XL.Worksheet;
    }
}

Notes:

  1. If ever you need to see everything that's going on in your code, including in the DebuggerNonUserCode methods you can turn off the "Just My Code" feature in Visual Studio by going to Tools->Options, then expanding the Debugging node, and selecting General:

    JustMyCodeOption

  2. Jacob Carpenter has written up a Generic version of the TryParse pattern for enums; Michael Goldobin has actually created a TryParse that avoids the exception altogether - but don't read about that yet, because then you won't need my trick!.
  3. The TryParse pattern is already implemented for parsing other primitive types; int, double, decimal, etc. all have their TryParse methods: go and vote for Enum to get it too. As Peter Richie points out in that Feedback issue I linked to, this forced use of exception handling that Enum.Parse makes you do violates the Framework Design Guidelines. Shame on you, Microsoft ;-).

4 comments:

Krzysztof Cwalina said...

I agree that Enum should have method TryParse, but I don't think the absence of the method violates an official guideline.

Moreover, TryParse is mainly for performance, less for debuggability, and definatelly not for simply avoiding exception handling.

Sam said...

Krzysztof,
Who am I to argue with the author of the guidelines? (And I'll admit that my introduction to this article was exaggerated, for effect.)

However, on page 186 of your book I find it written "DO NOT use exceptions for the normal flow of control if possible". The passage goes on to say that "framework designers should design API's so users can write code that does not throw exceptions."

In order to avoid exceptions when parsing user input involving Enum values, you have to take a much longer route round, as Michael Goldobin did at the link above. This seems to me to go against the guidelines.

Will we see TryParse in .Net 4.0?

Anyway, thanks for leaving your thoughts!

Krzysztof Cwalina said...

:-)
I can see how the guideline can be interpreted the way you did (and is probably written with a bit too strong language). But the point of the guideline was to strongly suggest that API designers provide APIs to allow users to check preconditions, if possible. If it's not possible, TryParse can be used to alleviate pref problems, if perf is deemed to be an issue. In case of Parse, checking preconditions is not practical, so TryParse might, but not automatically, apply.
Too strict interpretation of the guideline could lead to Try* methods sprinkled all over APIs. It seems like it would be bad, i.e. lots of unnecessary noise. Also, we would pretty much be back to the world of error codes (worse error codes with 2 values only). This would erase benefits of exceptions that are described at the beginning of the FDG chapter.

Anonymous said...

This is going to be so useful to suppress those BindingFailure exceptions you have no control over when using XmlSerializer! Thanks!

Post a Comment