Skip to content

Visual Studio Debugger Display Attributes and Other Features

Gary edited this page Aug 27, 2014 · 1 revision

Table of Contents

You can enhance debugging in Visual Studio by using debugger display attributes and other facilities in C#. This section shows how the DomNode information display, described in Debugging the DOM with Visual Studio, was implemented using these features.

Overriding ToString Method

The string returned from an object's ToString() method is displayed in the object's "Value" column in Visual Studio, so you can display a different value by overriding ToString() for the object's class.

The DomNode class takes advantage of this to provide more information about a node:

public override string ToString()
{
    if (m_type != null)
        return string.Format("0x{0:x}, {1}", GetHashCode(), m_type);

    return base.ToString();
}

This override method makes the Visual Studio debugger display the DomNode's hash code and DomNodeType:

  • GetHashCode() returns the object's hash code, generated by System.GetHashCode().
  • The m_type field contains the DomNode's DomNodeType.

Defining Properties to Display Information

You can define properties as convenient holders for all the information you want to display about a class. The DomNode class does this with its _DebugInfo property:

private DomNodeDebugger _DebugInfo
{
    get { return new DomNodeDebugger(this); }
}

The method simply returns a newly constructed DomNodeDebugger object. This property begins with "_" so it appears first in the list of DomNode's properties. This property adds no additional capability to the class, nor does it change the class's operation.

The DomNodeDebugger class is simply a package for all the information that's useful to see while debugging the DOM. It consists of a set of properties showcasing this useful data:

// The properties of this class are designed to appear in the Visual Studio debugger in
//  a useful way. For example, IList<> is more useful than IEnumerable<> in the debugger view.
private class DomNodeDebugger
{
    public DomNodeDebugger(DomNode node)
    {
        m_node = node;
    }

    public IList<AttributeDebugger> Attributes
    {
        get { return m_node.Type.Attributes.Select(attrInfo => new AttributeDebugger(attrInfo, m_node.GetAttribute(attrInfo))).ToList(); }
    }

    public IList<ChildDebugger> Children
    {
        get { return m_node.Children.Select(child => new ChildDebugger(child.ChildInfo, child)).ToList(); }
    }

    public IList<object> Extensions
    {
        get
        {
            var extensions = new List<object>();
            for( int i = m_node.Type.FirstExtensionIndex; i < m_node.Type.FieldCount; i++)
                extensions.Add(m_node.m_data[i]);
            return extensions;
        }
    }

    public IList<ListenerDebugger> AttributeChangingListeners
    {
        get { return m_node.GetAttributeChangingHandlers().Select(listener => new ListenerDebugger(listener)).ToList(); }
    }

    public IList<ListenerDebugger> AttributeChangedListeners
    {
        get { return m_node.GetAttributeChangedHandlers().Select(listener => new ListenerDebugger(listener)).ToList(); }
    }

    public IList<ListenerDebugger> ChildInsertingListeners
    {
        get { return m_node.GetChildInsertingHandlers().Select(listener => new ListenerDebugger(listener)).ToList(); }
    }

    public IList<ListenerDebugger> ChildInsertedListeners
    {
        get { return m_node.GetChildInsertedHandlers().Select(listener => new ListenerDebugger(listener)).ToList(); }
    }

    public IList<ListenerDebugger> ChildRemovingListeners
    {
        get { return m_node.GetChildRemovingHandlers().Select(listener => new ListenerDebugger(listener)).ToList(); }
    }

    public IList<ListenerDebugger> ChildRemovedListeners
    {
        get { return m_node.GetChildRemovedHandlers().Select(listener => new ListenerDebugger(listener)).ToList(); }
    }

    public override string ToString()
    {
        return "Additional debug info";
    }

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly DomNode m_node;
}

DomNodeDebugger consists of a set of properties containing DomNode information. Note that the property getters return an IList<T>, because that displays much more nicely in the debugger than an IEnumerable<T>. Each property returns an IList<T> of a another special debugging class that is a data container, such as AttributeDebugger. These secondary classes can contain further information to display in their properties and fields. For details on how such classes are defined, see DebuggerDisplayAttribute Class.

Simply having the _DebugInfo property that displays the contents of the DomNodeDebugger class is a major improvement over the default view of DomNode in the debugger. However, this display can be enhanced even further by using debugger attributes. For details, see Debugger Display Attributes.

Note that DomNodeDebugger overrides ToString() to create an explanatory object label, which appears as the DomNodeDebugger object's value, as described in Overriding ToString Method.

Debugger Display Attributes

Visual Studio has a set of attribute classes whose purpose is to format information for displaying in the debugger. For a description of these classes, see the MSDN article Enhancing Debugging with the Debugger Display Attributes.

The DomNodeDebugger class described in Defining Properties to Display Information uses these attributes to better present information, as illustrated in Debugging the DOM with Visual Studio.

DebuggerDisplayAttribute Class

DebuggerDisplayAttribute controls what is displayed for the attributed object. Its constructor's argument is a string that is displayed in the "Value" column for instances of the attributed type. Thus, it functions similarly to ToString(). If you have overridden ToString(), you do not need to use DebuggerDisplayAttribute. If you use both, the DebuggerDisplayAttribute attribute takes precedence over ToString().

In addition, DebuggerDisplayAttribute has a Name property whose value is displayed in the debugger in the "Name" column.

For instance, DomNodeDebugger.Attributes returns an IList<AttributeDebugger>, and the container class AttributeDebugger is attributed this way:

[DebuggerDisplay("{Value}", Name = "{AttributeInfo}")]
private class AttributeDebugger
{
    public AttributeDebugger(AttributeInfo info, object value)
    {
        AttributeInfo = info;
        Value = value;
    }

    public readonly AttributeInfo AttributeInfo;
    public readonly object Value;
}

AttributeDebugger is a container for AttributeInfo for the DomNode. Setting the Name property in DebuggerDisplayAttribute indicates that the string value of AttributeInfo appears in the "Name" column. The value of DebuggerDisplayAttribute's constructor's parameter appears in the "Value" column. This illustration shows the display produced by this class using this attribute:

The opened "name" node shows AttributeDebugger's fields AttributeInfo and Value. Note that the contents of the "Name" and "Value" columns do indeed match AttributeInfo and Value.

DebuggerBrowsableAttribute Class

DebuggerBrowsableAttribute specifies how a field or property is displayed in the debugger. Its constructor takes one of the DebuggerBrowsableState enumeration values:

  • Never: Don't display this attributed member in the debugger.
  • RootHidden: The member is not shown, but its constituent objects are displayed if it is an array or collection.
  • Collapsed: The member is shown but not expanded in the display.
The ChildDebugger class uses this attribute to simplify the debugger display. The DomNodeDebugger class uses ChildDebugger to display information about each child node in its Children property:
public IList<ChildDebugger> Children
{
    get { return m_node.Children.Select(child => new ChildDebugger(child.ChildInfo, child)).ToList(); }
}

Here's how the ChildDebugger class uses attributes:

[DebuggerDisplay("{Child}", Name = "{ChildInfo}")]
private class ChildDebugger
{
    public ChildDebugger(ChildInfo info, DomNode child)
    {
        ChildInfo = info;
        Child = child;
    }

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public readonly ChildInfo ChildInfo; //is visible inside the child; no need to show it at top-level.
    [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
    public readonly DomNode Child; //no need to show "Child" property; go straight to DomNode members.
}

Similarly to AttributeDebugger, ChildDebugger uses DebuggerDisplayAttribute to show ChildInfo for the name and the child DomNode for the value.

In addition, DebuggerBrowsableAttribute marks the ChildInfo and Child fields to restrict their display. To see what this attribute does, here's the DomNodeDebugger.Children node displayed with DebuggerBrowsableAttribute marking the fields in ChildDebugger:

Here is DomNodeDebugger.Children displayed without DebuggerBrowsableAttribute:

Note the differences under the opened "module" node, the first DomNode child. Without DebuggerBrowsableAttribute on the Child field to hide its root, an extra Child node appears, which is redundant because the DomNode's information is already displayed. Without DebuggerBrowsableAttribute on the ChildInfo field to never display it, there's also an additional ChildInfo node, which is also redundant, because that node is already in the child's displayed information.

Topics in this section

Clone this wiki locally