Attributes That Do Nothing

Some attributes in the .NET Framework don’t do anything. That is, there mere existence decorating your assemblies (and their types and members) has no discernable impact on runtime behavior. They are not used by the CLR for things like serialization and they are not used by applications through reflection. Pointless? Not at all.

A great example of this is the DebuggerDisplay attribute. It allows you to control how a variable is displayed in the debugger. Consider the following C# program:

enum SarcasmLevel
{
    Low,
    Medium,
    High
};

class Quote
{
    public string Statement;
    public SarcasmLevel Sarcasm;
}

class Program
{
    static void Main()
    {
        Quote quote1 = new Quote();
        quote1.Statement = "Veteran driver developers love the thought of UMDF.";
        quote1.Sarcasm = SarcasmLevel.High;

        Quote quote2 = new Quote();
        quote2.Statement = "UMDF threatens driver developer job security.";
        quote2.Sarcasm = SarcasmLevel.Low;

        Debugger.Break();
    }
}

Running this program in the debugger will result in a Locals window looking something like this:

 

Not especially useful. Now let’s add the DebuggerDisplay attribute.

[DebuggerDisplay("{Statement} Sarcasm: {Sarcasm}")]
class Quote
{
    public string Statement;
    public SarcasmLevel Sarcasm;
}

The next time you run the code the Locals window appears with much more useful information right off the bat.

Another handy attribute is the Conditional attribute. Consider the following program:

class Program
{
    static void ShareSecret(string secret)
    {
        Debug.WriteLine("About to share secret...");
        Debug.Assert(null != secret);

        if (null == secret)
        {
            throw new ArgumentNullException();
        }

        Console.WriteLine(secret);
        Debug.WriteLine("All done sharing secret.");
    }

    static void Main()
    {
        ShareSecret("I'm a closet C# developer.");
    }
}

The Debug class has a number of Assert and WriteLine overloaded methods all of which are attributed with the Conditional attribute to indicate that calls to those methods should only be included in the MSIL instruction stream produced by the compiler if a particular preprocessing identifier has been defined. The idea is that in release builds the ShareSecret method will be compiled to the MSIL equivalent of this:

static void ShareSecret(string secret)
{
    if (null == secret)
    {
        throw new ArgumentNullException();
    }

    Console.WriteLine(secret);
}

That’s certainly a lot more versatile than the macros you’d need to employ in C and C++ for conditional code. Speaking of C++...

If you’re a regular reader you may be wondering at this point why all my code samples are written in C#. The Visual C++ compiler has a dark little secret.

ShareSecret("Visual C++ ignores attributes that do nothing!");

Although it’s no surprise (unfortunately) that the Visual C++ managed code development experience isn’t as rich as the Visual C# development experience, it may come as a surprise that the following C++/CLI port of the C# ShareSecret method doesn’t do what you’d expect:

void ShareSecret(String^ secret)
{
    Debug::WriteLine("About to share secret...");
    Debug::Assert(nullptr != secret);

    if (nullptr == secret)
    {
        throw gcnew ArgumentNullException;
    }

    Console::WriteLine(secret);
    Debug::WriteLine("All done sharing secret.");
}

The problem of course is that the Visual C++ compiler leaves those “debug-only” statements in release builds and this causes undesired side-effects.

For starters, it will send the debug messages to any program monitoring debug output even in release builds. At best this is an annoyance. I certainly can’t stand programs that unnecessarily fill up my DebugView window when I’m busy debugging. It can also hurt performance and lead to more serious concerns such as inadvertent information disclosure.

Another problem is that it may well change your application’s behavior. Using an assertion to check function preconditions is a popular technique to quickly catch errors at runtime and direct the debugger to break at that point (instead of waiting for an exception to propagate). The subsequent if statement is there for release builds to guarantee the same preconditions in a programmatic and predictable manner. In other words, developers using this function expect it to throw an exception which they can catch and handle appropriately. Unfortunately, because Visual C++ doesn’t honor the Conditional attribute the exception cannot be handled before first being confronted by the breakpoint caused by the assertion.

And the solution is?

Well I’ve logged a bug with the Visual C++ team but it appears to not be a high priority item and won’t be fixed for the next release.

In the meantime there isn’t all that much that you can do short of providing a complete replacement for classes like the Debug class from the Diagnostics namespace. Using macros and the preprocessor isn’t very effective since the preprocessor has no understanding of overloaded functions. You end up having to pick one overload and sticking with it. For example, here is how you can implement the ASSERT macro for managed code:

#if _DEBUG
#define ASSERT(expr) System::Diagnostics::Debug::Assert(expr)
#else
#define ASSERT(expr) ((void)0)
#endif

What’s the moral of the story? Know what your compiler is doing. Don’t make assumptions about attributes targeted at compilers and linkers.


© 2006 Kenny Kerr

4 Comments

Comments have been disabled for this content.