Chapter 50
Public Class Customer
<DebuggerBrowsable(DebuggerBrowsableState.Collapsed)> _
Private Orders as List(Of Order)
End Class
Figure 50-1 shows the same snippet of code with the DebuggerBrowsable initially set to Collapsed (or not specified). The middle snippet shows the RootHidden value, where the actual Orders item does not appear, just the contents of the collection. Finally, the Never value is used, in which case the Orders member does not appear at all.
Figure 50-1
DebuggerDisplay
When you hover your mouse over a variable while you are in Break mode, the first thing you will see in the tooltip is the type of object you are hovering over. In Figure 50-1, a mouse was initially hovering over the Customer class, followed by the Order class. This information is not particularly useful, as most of the time you have a fairly good idea about the type of object you are dealing with. It would be better for this single line to contain more useful information about the object. This is the case for well-known types such as strings and integers where the actual value is displayed.
The DebuggerDisplay attribute can be used to change the single line representation of the object from the default full class name. This attribute takes a single parameter, which is a String. The format of this String can accept member injections using the String.Format breakout syntax. For example, the attributes applied to the Customer and Order classes might be as follows:
<DebuggerDisplay(“Customer {Name} has {Orders.Count} orders”)> _ Public Class Customer
<DebuggerDisplay(“Order for {Quantity} of {Description} made on {DateOrdered}”)> _ Public Class Order
This would give you the debugger output shown in Figure 50-2, which indicates that customer Bob has one order, which, as you can see from the description, was made on December 27 for five cartons of milk.
Figure 50-2
Debugging Proxies and Visualizers
Looking back at the syntax for the DebuggerDisplay attribute, you can see that the output string consists of both static text and field and property information from the object. For example, the Name property for the Customer object is referenced using the {Name} syntax within the static text.
DebuggerHidden
The DebuggerHidden attribute can be added to code that you don’t want to step through when debugging. Code marked with this attribute is stepped over and does not support breakpoints. If this code makes a call to another method, the debugger steps into that method. Taking the following code snippet, a breakpoint can be set in both ClickHandler and NotSoHiddenMethod:
Private Sub ClickHandler(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles BtnTest.Click
HiddenMethod() End Sub
<DebuggerHidden()> _ Public Sub HiddenMethod()
Console.WriteLine(“Can’t set a breakpoint here”)
NotSoHiddenMethod()
End Sub
Public Sub NotSoHiddenMethod()
Console.WriteLine(“Can set a breakpoint here!”)
End Sub
If you step through this code, the debugger goes from the call to HiddenMethod in the ClickHandler method straight to the NotSoHiddenMethod. The call stack at this point is shown in Figure 50-3, and you can see that HiddenMethod does not appear in the stack.
Figure 50-3
DebuggerStepThrough
Like the DebuggerHidden attribute, when the DebuggerStepThrough attribute is applied to a piece of code, that code is stepped over when debugging regardless of whether this code calls other methods.
Similar to the DebuggerHidden attribute, breakpoints cannot be set within a block of code marked with the DebuggerStepThrough attribute. However, if a breakpoint is set within a section of code that is called by that code, the attributed code will be marked as external code in the call stack. This is illustrated in Figures 50-4 and 50-5.
Chapter 50
Figure 50-4
Figure 50-5
Visual Studio 2005 introduces 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. 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.
DebuggerNonUserCode
The DebuggerNonUserCode attribute combines the DebuggerHidden and DebuggerStepThrough attributes. When the Just My Code option is selected, breakpoints cannot be set in blocks of code marked with this attribute, and the code will appear as external code in the call stack. However, stepping through code will step into any code called by that block of code in the same way it does for the
DebuggerHidden attribute.
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:
Public Class Customer
...
Private m_CountAccessed As Integer
Public ReadOnly Property OrderCount() As Integer
Get
m_CountAccessed += 1
Debugging Proxies and Visualizers
Return Me.Orders.Count
End Get
End Property
Public ReadOnly Property CountAccessed() As Integer
Get
Return Me.m_CountAccessed
End Get
End Property
End Class
Figure 50-6 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.
Figure 50-6
The first line in the tooltip is the same as what you created using the DebuggerDisplay attribute. To generate the rest of the tooltip, 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:
<DebuggerDisplay(“Customer {Name} has {Orders.Count} orders”)> _ <DebuggerTypeProxy(GetType(Customer.CustomerProxy))> _
Public Class Customer
...
Private m_CountAccessed As Integer
Public ReadOnly Property OrderCount() As Integer
Get
m_CountAccessed += 1
Return Me.Orders.Count
End Get
End Property
Public ReadOnly Property CountAccessed() As Integer
Get
Return Me.m_CountAccessed
End Get
End Property
Public Class CustomerProxy
Public Name As String
Chapter 50
Public NumberOfOrders As Integer
Public MaximumQuantity As Integer = 0
Public MinimumQuantity As Integer = Integer.MaxValue
Public ItemsOrdered As String
Public Sub New(ByVal c As Customer) Me.Name = c.m_Name Me.NumberOfOrders = c.m_orders.Count For Each o As Order In c.m_orders
Me.MaximumQuantity = Math.Max(o.Quantity, Me.MaximumQuantity) Me.MinimumQuantity = Math.Min(o.Quantity, Me.MinimumQuantity) If Not Me.ItemsOrdered = “” Then Me.ItemsOrdered &= “, “ Me.ItemsOrdered &= o.Description
Next End Sub
End Class
End Class
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.
In fact, the CustomerProxy class breaks another good coding practice, which states you should have private fields with public accessors. When the debugger tooltips are generated, both private and public fields and properties are displayed. As such, if you were to follow good coding practice here, you would end up with 10 elements being displayed, which only adds confusion.
The Full Picture
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 the Options dialog, accessible from the Tools menu, there is a node that covers Debugging. This includes nodes for controlling both the Edit and Continue feature and just-in-time debugging. To ignore the proxy type and see the original structure of a third-party component, check the box on the General tab that says “Show raw structure of objects in variables window.”
V isualizers
The last part of this chapter looks at a new concept added to Visual Studio 2005 to assist with debugging 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.
Debugging Proxies and Visualizers
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 50-7 shows the Text Visualizer dialog that appears.
Figure 50-7
Before you can start writing a visualizer, you need to add a reference to the Microsoft.VisualStudio
.DebuggerVisualizers namespace. To do this, right-click in the Solution Explorer and select Add Reference from the context menu.
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 50-8 shows a simple form, CustomerForm, that can be used to represent the customer information. This is a standard Windows form with a BindingSource used to link the TextBox and DataGrid to the Customer object.
Figure 50-8
The next stage is to wire this form up to be used as the visualizer for the Customer class. This is done by creating the nested CustomerVisualizer class, which inherits from the DialogDebuggerVisualizer abstract class, as shown in the following code:
Chapter 50
<Serializable()> _
<DebuggerDisplay(“Customer {Name} has {Orders.Count} orders”)> _ <DebuggerTypeProxy(GetType(Customer.CustomerProxy))> _ <DebuggerVisualizer(GetType(Customer.CustomerVisualizer))> _ Public Class Customer
...
Public Class CustomerVisualizer
Inherits DialogDebuggerVisualizer
Protected Overrides Sub Show _
(ByVal windowService As IDialogVisualizerService, _
ByVal objectProvider As IVisualizerObjectProvider)
Dim c As Customer = CType(objectProvider.GetObject, Customer)
Dim cf As New CustomerForm cf.CustomerBindingSource.DataSource = c
windowService.ShowDialog(cf) End Sub
End Class
End Class
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 debuggee process (the process being debugged) to the debugger process (the process that is doing the debugging and will show 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. This is done 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 create and bind the Customer object to an instance of the CustomerForm. Finally, use the ShowDialog method on the WindowSerivce 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 8\Common7\Packages\Debugger\Visualizers directory, or the My Documents\Visual Studio 2005\Visualizers directory.
Debugging Proxies and Visualizers
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 DataTip. 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 debuggee process. This can be done using the
ReplaceObject method on the ObjectProvider, which gives you a CustomerVisualizer as follows:
Public Class CustomerVisualizer
Inherits DialogDebuggerVisualizer
Protected Overrides Sub Show _
(ByVal windowService As IDialogVisualizerService, _ ByVal objectProvider As IVisualizerObjectProvider)
Dim c As Customer = CType(objectProvider.GetObject, Customer)
Dim cf As New CustomerForm cf.CustomerBindingSource.DataSource = c
If windowService.ShowDialog(cf) = Windows.Forms.DialogResult.OK Then objectProvider.ReplaceObject(c)
End If End Sub
End Class
Summar y
Debugging applications is one of the most time-consuming and frustrating activities in the development cycle. In this chapter you learned how you can take charge of Visual Studio 2005 by customizing the debugging experience.
Using debugging proxy types and visualizers, you can control how information is presented to you while you are debugging your application. This means that you can easily determine when your application is not functioning correctly and be able to trace the source of the issue.
Maintaining Web
Applications
With Visual Studio 2005, debugging solutions for the web is just as straightforward as doing the same for Windows-based applications. You can use many of the same debugging windows already discussed in previous chapters, as well as deal with errors through the Exception Assistant. However, there are some minor differences and additional features specific to web applications that you can use to target your debugging practices more closely to the web paradigm.
In addition to the available debugging techniques, ASP.NET 2.0 also provides you with a comprehensive tracing capability, and even the capability to perform health monitoring on your system to ensure it is running in the manner you expect, and exposing problematic scenarios when it doesn’t.
Debugging
Before you can perform any level of debugging in a web application, you first need to ensure that ASP.NET debugging is enabled in your web site. To make sure it’s active, right-click the project entry in the Solution Explorer and select Property Pages from the context menu.
When the Property Pages dialog is displayed, navigate to the Start Options page, and ensure that the ASP.NET debugger option is checked, as illustrated in Figure 51-1. While you’ve got this page open, you can also customize how the web site is to be started, including not opening any specific page, but running the server so it listens for a request from another application.
In addition, if you want to be able to include unmanaged code or even stored procedures in your debugging of the web applications, you can activate the Native code and SQL Server debuggers here (take a look at Chapter 52 for more information on debugging stored procedures).