that is called by that code, the attributed code
will be marked as external code in the
call stack. This is illustrated in Figure 41-6,
which shows the code that was listed in
the previous section. However, in this case
DebuggerStepThrough has been set on fiGure 41-6
HiddenMethod instead of DebuggerHidden.
Visual Studio 2010 supports the Just My Code option, configurable from the Debugging node in
the Options dialog (select Tools . Options). Unchecking this option makes all code contained
within your application appear in the call
stack, as shown in Figure 41-7. This includes
designer and other generated code that you
might not want to debug. Once this option
is unchecked, breakpoints can also be set in
blocks of code marked with this attribute.
fiGure 41-7
You can also right-click the call stack and select “Show External Code” to reveal
any hidden or designer code.
debuggernonusercode
The DebuggerNonUserCode attribute combines the DebuggerHidden and DebuggerStepThrough
attributes. In the default Visual Studio configuration, code marked with this attribute appears
as external code in the call stack. As was the case with the DebuggerStepThrough attribute, you
cannot set breakpoints in blocks of code marked with this attribute. Stepping through code steps
into any code called by that block of code in the same way it does for the DebuggerHidden
attribute.
debuggerstepperboundary
DebuggerStepperBoundary is the most obscure of all of the Debugger attributes, because it
comes into effect only under specific conditions. It is used to avoid a misleading debugging
experience that can occur when a context switch is made on a thread within the boundaries of the
Type Proxies .
863
DebuggerNonUserCode attribute. It is entirely possible in this scenario that the next user-supplied
code module stepped into may not actually relate to the code that was in the process of being
debugged. To avoid this invalid debugging behavior, the DebuggerStepperBoundary attribute,
when encountered under this scenario, will escape from stepping through code and instead resume
normal execution of the code.
tyPe Proxies
So far, you have seen how you can modify the tooltip to show information that is more relevant
to debugging your application. However, the attributes discussed so far have been limited in how
they control what information is presented in the expanded tree. The DebuggerBrowsable attribute
enables you to hide particular members, but there is no way to add more fields. This is where the
DebuggerTypeProxy attribute can be used to provide you with complete control over the layout of
the tooltip.
The other scenario where a type proxy is useful is where a property of a class changes values
within the class. For example, the following snippet from the Customer class tracks the number
of times the OrderCount property has been accessed. Whenever the tooltip is accessed, the
CountAccessed property is incremented by one:
c#
public class Customer
{
private int m_CountAccessed;
public int OrderCount
{
get
{
m_CountAccessed++;
return this.Orders.Count;
}
}
public int CountAccessed
{
get
{
return this.m_CountAccessed;
}
}
}
Figure 41-8 illustrates the tooltip you want to be shown for
the Customer class. Instead of showing the full list of orders to
navigate through, it provides a summary about the number of
orders, the maximum and minimum order quantities, and a list
of the items on order.
The first line in the tooltip is the same as what you created using
the DebuggerDisplay attribute. To generate the rest of the tooltip,
fiGure 41-8
864 .
chaPter 41 dATATipS, debug proxieS, And ViSuAlizerS
you need to create an additional class that will act as a substitute when it comes to presenting
this information. You then need to attribute the Customer class with the DebuggerTypeProxy
attribute so the debugger knows to use that class instead of the Customer class when displaying the
tooltip. The following code snippet shows the CustomerProxy class that has been nested within
the Customer class:
c#
[DebuggerDisplay("Customer {CustomerName} has {Orders.Count} orders")]
[DebuggerTypeProxy(typeof(Customer.CustomerProxy))]
public class Customer
{
private int m_CountAccessed;
public int OrderCount
{
get
{
m_CountAccessed++;
return this.Orders.Count;
}
}
public int CountAccessed
{
get
{
return this.m_CountAccessed;
}
}
public class CustomerProxy
{
public string CustomerName;
public int NumberOfOrders;
public decimal MaximumTotal = decimal.MinValue;
public decimal MinimumTotal = decimal.MaxValue;
public CustomerProxy(Customer c)
{
this.CustomerName = c.m_CustomerName;
this.NumberOfOrders = c.m_Orders.Count;
foreach (Order o in c.m_Orders)
{
this.MaximumTotal = Math.Max(o.Total, this.MaximumTotal);
this.MinimumTotal = Math.Min(o.Total, this.MinimumTotal);
}
}
}
}
There are very few reasons why you should create public nested classes, but a type proxy is a good
example because it needs to be public so it can be specified in the DebuggerTypeProxy attribute,
and it should be nested so it can access private members from the Customer class without using the
public accessors.
Visualizers .
865
raw View
On occasion, you might want to ignore the proxy type. For example, this might be true if you
are consuming a third-party component that has a proxy type defined for it that disguises the
underlying data structure. If something is going wrong with the way the component is behaving,
you might need to review the internal contents of the component to trace the source of the issue.
In Figure 41-8, you may have noticed at the bottom of the tooltip was a node titled Raw View.
Expanding this node displays the debugger tooltip as it is normally shown, without any proxy types
or debugger display values.
In addition, you can turn off all type proxies in Visual Studio through the Tools . Options menu.
Under the Debugging node, check the box that says Show Raw Structure of Objects in Variables
Windows. Doing this prevents all type proxies and debugger displays from being shown.
Visualizers
This part of the chapter looks at a feature in Visual Studio 2010 that can be used to help debug
more complex data structures. Two of the most common data types programmers work with are
Strings and DataTables. Strings are often much larger than the area that can be displayed within a
tooltip, and the structure of the DataTable
object is not suitable for displaying in a
tooltip, even using a type proxy. In both of
these cases, a visualizer has been created that
enables the data to be viewed in a sensible
format.
Once a visualizer has been created for a
particular type, a magnifying glass icon
appears in the first line of the debugger
tooltip. Clicking this icon displays the
visualizer. Figure 41-9 shows the Text
Visualizer dialog that appears.
Before you can start writing a visualizer, you need to add a reference to the Microsoft.
VisualStudio.DebuggerVisualizers namespace. To do this, right-click the project in the Solution
Explorer and select Add Reference from the context menu. You should also add this namespace as
an import to any classes for which you plan to create debugger visualizers.
fiGure 41-9
The version of Microsoft.VisualStudio.DebuggerVisualizers that ships
with Visual Studio 2010 is valid only for projects that target version 4.0 of the
Microsoft .NET Framework.
866 .
chaPter 41 dATATipS, debug proxieS, And ViSuAlizerS
A visualizer is typically made up of two parts: the class that acts as a host for the visualizer and
is referenced by the DebuggerVisualizer attribute applied to the class being visualized, and
the form that is then used to display, or visualize, the class. Figure 41-10 shows a simple form,
CustomerForm, which can be used to represent the customer information. This is just an ordinary
Windows Form with a couple of TextBox controls, a DataGridView control, and a Button. The
only unique aspect to this form is that it has been marked as Serializable, and its constructor has
been changed to accept a Customer object, from which the customer information is extracted and
displayed, as shown in the following code:
c#
[Serializable()]
public partial class CustomerForm : Form
{
public CustomerForm(Customer c)
{
InitializeComponent();
this.txtCustomerId.Text = c.CustomerId.ToString();
this.txtCustomerName.Text = c.CustomerName;
this.dgOrders.DataSource = c.Orders;
}
private void btnOk_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
}
The next stage is to wire this form up to
be used as the visualizer for the Customer
class. You do this by creating the nested
CustomerVisualizer class, which inherits
from the DialogDebuggerVisualizer
abstract class, as shown in the following
code:
c#
[Serializable()]
[DebuggerDisplay("Customer {CustomerName} has {Orders.Count} orders")]
[DebuggerTypeProxy(typeof(Customer.CustomerProxy))]
[DebuggerVisualizer(typeof(Customer.CustomerVisualizer))]
public class Customer
{
//...
public class CustomerVisualizer : DialogDebuggerVisualizer
{
protected override void Show(
IDialogVisualizerService windowService,
IVisualizerObjectProvider objectProvider)
fiGure 41-10
advanced Techniques .
867
{
Customer c = (Customer)objectProvider.GetObject();
CustomerForm cf = new CustomerForm(c);
windowService.ShowDialog(cf);
}
}
}
Unlike the type proxy, which interacts with the actual Customer object being debugged, visualizers
need to be able to serialize the class being debugged so the class can be moved from the process
being debugged to the process that is doing the debugging, and will subsequently be shown in the
visualizer. As such, both the Customer and Order classes need to be marked with the Serializable
attribute.
The Show method of the CustomerVisualizer class does three things. To display the Customer
object being debugged, first you need to get a reference to this object. You do this via the GetObject
method on the ObjectProvider object. Because the communication between the two processes is
done via a stream, this method does the heavy lifting associated with deserializing the object so you
can work with it.
Next you need to pass the Customer object to a new instance of the CustomerForm. Finally, use
the ShowDialog method on the WindowService object to display the form. It is important that you
display the form using this object because it will ensure that the form is displayed on the appropriate
UI thread.
Lastly, note that the CustomerVisualizer class is referenced in the DebuggerVisualizer attribute,
ensuring that the debugger uses this class to load the visualizer for Customer objects.
As a side note, if you write components and want to ship visualizers separately from the components
themselves, visualizers can be installed by placing the appropriate assembly into either the C:\
Program Files\Microsoft Visual Studio 10.0\Common7\Packages\Debugger\Visualizers
directory (Program Files (x86) on 64-bit Windows), or the Documents\Visual Studio 2010\
Visualizers directory.
adVanced techniques
Thus far, this chapter has covered how to display and visualize objects you are debugging. In earlier
chapters, you learned how to modify field and property values on the object being debugged via the
data tip. The missing link is being able to edit more complex data objects. The final section in this
chapter looks at how to extend your visualizer so you can save changes to the Customer object.
saving changes to your object
When you created the CustomerVisualizer, you had to retrieve the Customer object from
the communication stream using the GetObject method. This essentially gave you a clone of the
Customer object being debugged to use with the visualizer. To save any changes you make in
the CustomerVisualizer, you need to send the new Customer object back to the process being
debugged. You can do this using the ReplaceObject method on the ObjectProvider, which gives
you a CustomerVisualizer.
868 .
chaPter 41 dATATipS, debug proxieS, And ViSuAlizerS
Before you can call the ReplaceObject method you will need to make some changes to pass the
modified Customer object back to the visualizer. This has been done by saving the Customer object
to an internal variable when it is initially passed into the class, and exposing this variable via a read-
only property. This is shown in the following code:
c#
[Serializable()]
public partial class CustomerForm : Form
{
public CustomerForm(Customer c)
{
InitializeComponent();
this.txtCustomerId.Text = c.CustomerId.ToString();
this.txtCustomerName.Text = c.CustomerName;
this.dgOrders.DataSource = c.Orders;
m_ModifiedCustomer = c;
}
private Customer m_ModifiedCustomer;
public Customer ModifiedCustomer
{
get
{
m_ModifiedCustomer.CustomerId = new Guid(txtCustomerId.Text);
m_ModifiedCustomer.CustomerName = txtCustomerName.Text;
m_ModifiedCustomer.Orders = (List<Order>)dgOrders.DataSource;
return m_ModifiedCustomer;
}
}
private void btnOk_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
}
You can now easily access the modified Customer object and save the changes back by calling the
ReplaceObject method as shown here:
c#
[Serializable()]
[DebuggerDisplay("Customer {CustomerName} has {Orders.Count} orders")]
[DebuggerTypeProxy(GetType(Customer.CustomerProxy))]