Using Visual Studio Debugger Attributes

Introduction

Visual Studio

provides a rich debugging experience to developers, helping them write robust

and bug free code. In simple projects, the inbuilt facilities of Visual Studio

debugger may be sufficient for your purpose, however, while debugging complex

projects you may want to enhance the debugging experience further. Luckily,

Visual Studio offers debugger attributes that help you do just that. Debugger

attributes allow you to customize the way Visual Studio debugger steps through

your code and also the display of your types. This article explains some of the

important debugger attributes along with debugger type proxies and visualizers.

Debugger Attributes at a Glance

Most of the debugger attributes are found in System.Diagnostics namespace.

In this article, you will learn to use the following debugger attributes:

  • DebuggerDisplay:

    This attribute allows you to display a custom piece of information about a

    type, such as Watch Window, Quick Watch Window and Data Tips, in the debugger

    variable windows.

  • DebuggerBrowsable:

    This attribute specifies how a class member is displayed in the debugger

    variable windows. You can also hide a member from appearing in the

    debugger variable windows.

  • DebuggerHidden:

    This attribute tells the Visual Studio debugger to step over the code

    instead of stepping into the code.

  • DebuggerStepThrough:

    This attribute tells the Visual Studio debugger to step over the code

    instead of stepping into the code but treats that code as external in the

    call stack.

  • DebuggerTypeProxy:

    For complex classes you may want to customize their appearance in the

    debugger variable windows. This attribute specifies a proxy type to be

    used for such customized display.

  • DebuggerVisualizer: You may want to view

    an object of your type in a special viewer rather than inbuilt debugger

    variable windows. This attribute helps you to link a custom viewer with

    your class.

Let’s examine how each on the above attributes works with an example. In

order to work with the examples that follow, you need to create a new Console

Application in C# and import System.Diagnostics namespace at the top of the

default class file.

DebuggerDisplay Attribute

Assume that you have a simple class as shown below:

class Program
{
  public List<string> Fruits = new List<string>();
  ...
  static void Main(string[] args)
  {
    Program p = new Program();
    p.Fruits.Add("Apple");
    p.Fruits.Add("Mango");
    p.Fruits.Add("Banana");
    ...
  }
}

Now, if you run the application and observe the Program class in Visual

Studio Watch window or simply hover your mouse over the object p, you will see

something like this:

Visual Studio Watch window

Figure 1: Visual Studio Watch window

Notice the Value column. It displays the fully qualified name of the class

under consideration. Many times, however, you are already familiar with this

piece of information and displaying it in debugger variable windows doesn’t

serve any useful purpose. It would be handy if we can see some useful

information about the class instead of its fully qualified name. The

[DebuggerDisplay] attribute allows you to do just that. Using the

[DebuggerDisplay] attribute you can specify custom information that will appear

in the debugger variable windows. The following fragment of code illustrates

how the [DebuggerDisplay] attribute can be used on the Program class.

[DebuggerDisplay("There are {Fruits.Count} delicious fruits!")]
class Program
{
  ...
}

Notice how Fruits.Count is used in the curly brackets ({…}) to output the

total number of items in the list of Fruits. The following figure shows the

same variable p after applying [DebuggerDisplay] attribute.

The variable p after applying [DebuggerDisplay] attribute

Figure 2: The variable p after applying [DebuggerDisplay] attribute

See how the fully qualified type name is replaced with the information from

[DebuggerDisplay] attribute.

DebuggerBrowsable Attribute

The [DebuggerBrowsable] attribute specifies how to display a field in

debugger variable windows. It also allows you to hide a field. Consider the

following piece of code:

[DebuggerDisplay("There are {Fruits.Count} delicious fruits!")]
class Program
{
    private List<string> lstFruits = new List<string>();
 
    public List<string> Fruits
    {
        get
        {
            return lstFruits;
        }
        set
        {
            lstFruits = value;
        }
    }
}

The above fragment of code modifies the Program class to have a property

named Fruits. The Fruits property wraps lstFruits variable. If you observe an

instance of Program class in Watch window it will appear as follows:

Program class in Watch window

Figure 3: Program class in Watch window

As you can see in the above figure, the same list of fruits appears twice –

once as a Fruits property and then as a lstFruits variable. This duplication is

unnecessary. You can safely hide the lstFruits variable because the Fruits

property is displaying the same information. Now, have a look at the following

code that makes use of [DebuggerBrowsable] attribute to achieve that :

[DebuggerDisplay("There are {Fruits.Count} delicious fruits!")]
class Program
{
     [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private List<string> lstFruits = new List<string>();
 
    [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
    public List<string> Fruits
    {
        get
        {
            return lstFruits;
        }
        set
        {
            lstFruits = value;
        }
    }
}

The [DebuggerBrowsable] applied on the lstFruits variable specifies that the

variable should never be displayed in the debugger variable windows. Similarly,

the [DebuggerBrowsable] attribute applied to the Fruits property specifies that

only the list items should be displayed in the debugger variable windows. The

DebuggerBrowsableState enumeration has three values – Never, Collapsed and

RootHidden. The following figure shows the effect of [DebuggerBrowsable]

attribute.

The Effect of [DebuggerBrowsable] attribute

Figure 4: The Effect of [DebuggerBrowsable] attribute

See how the lstFruits variable has disappeared from the Watch window. Also,

observe how individual items from the Fruits list appear directly under p

because of the DebuggerBrowsableState.RootHidden value.

DebuggerHidden Attribute

Sometimes you may not want to step into certain pieces of code. Consider,

for example, that you have a method that in turn calls a few other auxiliary

methods for the sake of tracing and logging. Now there is no need to step into

these auxiliary methods every time you debug the main method. In such cases the

auxiliary methods can be marked with the [DebuggerHidden] attribute. After

applying the [DebuggerHidden] attribute, debugger will always step over these

methods even if they have breakpoints. The Call Stack window won’t show the

methods decorated with  [DebuggerHidden] attribute. Have a look at the

following code that illustrates the use of the [DebuggerHidden] attribute.

static void Main(string[] args)
{
    Program p = new Program();
    p.Fruits.Add("Apple");
    p.Fruits.Add("Mango");
    p.Fruits.Add("Banana");
 
    p.DisplayFruits();
 
    Console.ReadLine();
}
 
[DebuggerHidden]
public void DisplayFruits()
{
    foreach (string s in Fruits)
    {
        Console.WriteLine(s);
    }
    HelloWorld();
}
 
public void HelloWorld()
{
    Console.WriteLine("Hello World!");
}

The Main() method calls the DisplayFruits() method that is decorated with

the [DebuggerHidden] attribute. DisplayFruits() method in turn calls the

HelloWorld() method. Before you run the above code, set breakpoints at the

lines marked in bold. If you run the above code and keep pressing F11 (step

into) you will observe that the debugger won’t enter the DisplayFruits() method

at all and the Call Stack window will resemble the image shown below:

The Call Stack window

Figure 5: The Call Stack window

As you can see, the call stack doesn’t show the DisplayFruits() method but

the output as seen in the console window will prove that it has been executed.

DebuggerStepThrough Attribute

The [DebuggerStepThrough] attribute is similar to the [DebuggerHidden]

attribute in that it steps over the code but it marks the code as ‘External’ in

the call stack window. Consider the following piece of code:

static void Main(string[] args)
{
    Program p = new Program();
    p.Fruits.Add("Apple");
    p.Fruits.Add("Mango");
    p.Fruits.Add("Banana");
 
    p.DisplayFruits2();
 
    Console.ReadLine();
}
 
[DebuggerStepThrough]
public void DisplayFruits2()
{
    foreach (string s in Fruits)
    {
        Console.WriteLine(s);
    }
    HelloWorld();
}
 
public void HelloWorld()
{
    Console.WriteLine("Hello World!");
}

Notice that the DisplayFruits2() method is now marked with the

[DebuggerStepThrough] attribute. If you debug the application by pressing F11

the behavior will be similar to the previous example but the call stack window

shows DisplayFruits2() method as ‘External Code’.

The Call Stack window shows the method as external code

Figure 6: The Call Stack window shows the method as external code

DebuggerTypeProxy Attribute

By default the Visual Studio debugger will show all of the members of a type

as-is i.e. the way they appear in the actual type. However, if a type is

complicated in nature this behavior can be troublesome. In such cases you may

want to alter the ‘view’ of a type so that it exposes information the way you

want during the debugging process. That is where Type Proxies come into the

picture. Consider a class named User as shown below:

public class User
{
    public string UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string Profile { get; set; }
}

The User class has six properties viz. UserId, FirstName, LastName, Email,

Password and Profile. In the Main() method you are using this class like this:

User obj = new User();
obj.UserId = "User1";
obj.FirstName = "Tom";
obj.LastName = "Jerry";
obj.Email = "tom@somedomain.com";
 
byte[] b = ASCIIEncoding.ASCII.GetBytes("password");
string str = Convert.ToBase64String(b);
 
obj.Password = str;
obj.Profile = "Hello world!";

You first create an instance of the User class and then assign various

property values. Notice how the Password property is being set. For the sake of

security the password is being encrypted (the above code uses Base64 encoding,

which is not secure as such. In real world cases you have some sound encryption

logic in place. Using Base64 encoding illustrates our point though.) and then

the encrypted value is assigned to the Password property. If you add a watch to

variable obj you will see something like this:

Add a Watch to variable obj

Figure 7: Add a Watch to variable obj

See how Password is being shown in Base64 encoding. During debugging you may

want to see the actual password rather than its Base64 representation.

Similarly, rather than viewing FirstName and LastName as two separate pieces

you may want to see the full name and may want to ignore the Profile property.

To accomplish such customization you need to create a Type Proxy and then

attach a type proxy to the User class using [DebuggerTypeProxy] attribute.

Let’s see how.

Create a new class named UserProxy as shown below:

public class UserProxy
{
    public string UserId { get; set; }
    public string DisplayName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
 
    public UserProxy(User u)
    {
        this.UserId = u.UserId;
        this.DisplayName = u.FirstName + " " + u.LastName;
        this.Email = u.Email;
        this.Password = GetPlainPassword(u.Password);
    }
 
    private string GetPlainPassword(string pwd)
    {
        byte[] b = Convert.FromBase64String(pwd);
        string str = ASCIIEncoding.ASCII.GetString(b);
        return str;
    }
}

The UserProxy class defines four properties viz. UserId, DisplayName, Email

and Password. The constructor of UserProxy class takes a parameter of type

User. Inside the constructor various property values are assigned. Notice that

DisplayName is a combination of FirstName and LastName. The password in Base64

format is first decoded (using GetPlainPassword() method) and its plain string

value is assigned to the Password property.

To specify that the UserProxy class will act as a type proxy for the User

class, mark the User class with the [DebuggerTypeProxy] attribute as shown

below:

[DebuggerTypeProxy(typeof(UserProxy))]
public class User
{
   ...
}

If you now debug the code you will find that even though you are viewing the

object of the User class, it shows information based on the UserProxy class in

the Watch window.

Information based on the UserProxy Class

Figure 8: Information based on the UserProxy Class

Notice how DisplayName and Password properties re-arrange the information

from the User object.

DebuggerVisualizer Attribute

Visual Studio debugger variable windows provide a good view of information

for primitive types and classes. However, at times it becomes difficult to read

properties and variable values. For example, consider the User class discussed

earlier. The Profile property of the User class holds a free form string data

and it is tedious to view it in debugger variable windows. Similar situations

can arise for types such as DataSets, DataTables and complex types. Luckily,

Visual Studio allows you to define your own dialog for displaying a type

information. Such a dialog is referred as a Visualizer. There are some inbuilt

visualizers too as shown below:

Text Visualizer

Figure 9: Text Visualizer

Visualizers are displayed when you click on the magnifying glass icon in any

of the debugger variable windows (see below).

Click the magnifying glass icon to display the Visualizer

Figure 10: Click the magnifying glass icon to display the Visualizer

In order to create a custom visualizer first you need to create a Windows

Forms based dialog that acts as the visualizer. You then need to create a

visualizer class inheriting from the DialogDebuggerVisualizer base class.

Finally, you need to mark a type with the [DebuggerVisualizer] attribute.

To create a custom visualizer for the User class, add a Windows Form to the

project and design it as shown below:

Create a custom visualizer

Figure 11: Create a custom visualizer

As you can see the above form displays user information in various Label

controls. The constructor of the Form has the following code:

public ProfileVisualizer(User usr)
{
    InitializeComponent();
    label1.Text = usr.UserId;
    label2.Text = usr.FirstName + " " + usr.LastName;
    label3.Text = usr.Email;
    textBox1.Text = usr.Profile;
}

The constructor accepts a User instance and then assigns property values to

Label controls.

Next, add a reference to Microsoft.VisualStudio.DebuggerVisualizers

assembly. To locate this assembly, use the Browse tab of Add Reference dialog

and navigate to <Installation folder for VS

2010>\Common7\IDE\ReferenceAssemblies\v2.0.

Add a reference to Microsoft.VisualStudio.DebuggerVisualizers assembly

Figure 12: Add a reference to Microsoft.VisualStudio.DebuggerVisualizers assembly

Next, create a new class named UserVisualizer and inherit it from

DialogDebuggerVisualizer class as shown below:

public class UserVisualizer : DialogDebuggerVisualizer
{
 
   protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
   {
        User usr = (User)objectProvider.GetObject();
        MyVisualizer form = new MyVisualizer(usr);
        windowService.ShowDialog(form);
   }
}

The DialogDebuggerVisualizer resides in the Microsoft.VisualStudio.DebuggerVisualizers

namespace, so ensure that this namespace is imported at the top of the class

file. Once inherited you need to override the Show() method. The Show() method

creates an instance of the Windows Forms dialog you created earlier and then

invokes the ShowDialog() method on the windowService parameter.

The last step is to attach the custom visualizer class to the User class

with the help of [DebuggerVisualizer] attribute.

...
[DebuggerVisualizer(typeof(UserVisualizer))]
public class User
{
    ...
}

Now, if you debug the application and click on the magnifying glass icon

next to the User object (obj variable) you will see the custom visualizer

populated with data from the User instance under consideration. 

Summary

Visual Studio debugger attributes allow you to customize and enhance your

debugging experience. This article discussed some of the useful debugger

attributes viz. DebuggerDisplay, DebuggerBrowsable, DebuggerHidden,

DebuggerStepThrough, DebuggerTypeProxy and DebuggerVisualizer. The

DebuggerDisplay attribute allows you to display a custom piece of information

about a type in the debugger variable windows. The DebuggerBrowsable attribute

specifies how a class member is displayed in the debugger variable windows. You

can also hide a member from appearing in the debugger variable windows. The

DebuggerHidden attribute tells the Visual Studio debugger to step over the code

instead of stepping into the code. The DebuggerStepThrough attribute tells the

Visual Studio debugger to step over the code instead of stepping into the code

but treats that code as external in the call stack. For complex classes you may

want to customize their appearance in the debugger variable windows. The

DebuggerTypeProxy attribute specifies a proxy type to be used for such

customized display. At times you may want to view an object of your type in a

special viewer rather than inbuilt debugger variable windows. The

DebuggerVisualizer attribute helps you to link a custom viewer with your class.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read