Advanced Debugging


Debugging Overview

Debugging techniques vary between project types. For example, the strategy for debugging a Microsoft Windows application is different from debugging a class library. This section provides an overview of debugging strategies for basic project types.

Debugging Windows Forms Projects

Of the project types, managing a debugging session for a Windows Forms project is probably the easiest. Initiate a debugging session to begin debugging a Windows Forms application.

Start a debugging session of a Windows Forms project with a start execution action, such as the Start Debugging menu command. When the execution starts, the Visual Studio debugger is attached to the application.

Set breakpoints before or during the debugging session. Windows Forms applications are event driven, and breakpoints can be placed in event handlers. Breakpoints can also be added to functions called from event handlers. When the breakpoint is hit, the debugging session is transferred to break mode. To run between breakpoints, use the Start Debugging menu command.

Attaching to a Running Process

You can attach the debugger to processes running outside of Visual Studio. At that time, a debug session is initiated. There are several reasons to attach to a process:

  • When a problem occurs in an application, you can immediately debug the situation. Attach the debugger and diagnose the problem.

  • You can debug a class library project in the context of a running process.

  • You can debug a production application. If you attach to an application that is not a debug build, an error message box is displayed. You can then proceed into a debugging session. However, source code, symbols, and other ingredients of a normal debugging session might not be available. Care must be taken when debugging a production application. Some actions can interfere with the normal execution of the program. For example, breakpoints can interrupt execution of the application and strand users.

Open the Attach To Process window to attach to a process. (From the Debug menu, choose the Attach To Process menu command.) Figure 12-1 shows the Attach To Process window. The Debug menu is not available when no solution is open. In that circumstance, choose the Attach To Process menu command from the Tools menu.

image from book
Figure 12-1: Attach To Process window

A list of processes is presented in the Attach To Process window. To attach to a specific process, select a process from the Available Processes list and then click the Attach button. If not a debug build, an error message box is displayed. The debugger can be attached to multiple processes. All the attached processes are dimmed in the process list. However, you can actively debug only a single process, which defaults to the last process attached. You can change a current process by using the Debug Location toolbar and the Process drop-down list, which is shown in Figure 12-2. You can also select the Debug Location toolbar from the Toolbars submenu on the View menu.

image from book
Figure 12-2: Debug Location toolbar

The Attach To Process window has several options. Transport is the transport used to connect to a target device or remote machine. Default connects to a local machine or remote machine using the Microsoft Visual Studio Remote Debugging Monitor (msvsmon.exe). The Qualifier window is the name of the target machine. The Attach To choice selects the target type as managed, native, script, or T-SQL. The default is managed. The Show Processes For All Users option lists processes from all users of the current logon session. For Terminal Service clients remotely connected to a machine, Show Processes In All Sessions displays the processes of the remote machine session. The process list is not static. As new processes start up and existing processes finish, the list of running processes changes. The Refresh button updates the list box to reflect any changes in the process list.

After attaching to an application, interrupt the application with a breakpoint. You can then use debugging techniques and the various debugging windows to debug the application. To set a breakpoint, open the source code of the application and insert a breakpoint. When execution hits the breakpoint, the application is interrupted. Alternatively, use the BreakAll menu command on the Debug menu.

Terminate the debugging session to detach the debugger from the running process. For example, the Stop Debugging menu command on the Debug menu terminates a debugging session and detaches from a running process. The running process will continue to execute.

Debugging Console Application Projects

Debugging a Console project is similar to debugging a Windows application, with some notable differences:

  • Console applications typically output to the Console window, but debug messages are normally displayed in the Output window. Displaying messages in separate windows has both advantages and disadvantages. Clarity in isolation of debug messages is the primary advantage. Parsing debug and normal messages in separate windows helps track both. Conversely, the advantage of a single window for all output is convenience. The developer need not toggle between the Console and Output windows. With the TraceSource type, you have the option of displaying trace messages in several locations, including the Console window, Output window, or both windows simultaneously.

  • Console applications often require command-line arguments. Open Project properties from the Project menu. From the Debug window, enter the command-line arguments as part of the Startup Options. Be sure to refresh the command-line arguments as needed.

  • In a debugging session, the results of a short running console application are only briefly displayed before the console window is closed. Therefore, the results may be difficult to view. Freeze the window by inserting a Console.ReadLine statement. Alternatively, after debugging the application, execute in release mode to persist the Console window. In that case, Press Any Key To Continue is displayed.

Debugging Class Library Projects

A class library project creates a dynamic-link library (DLL), which does not execute directly. This makes the proposition of debugging more challenging. The DLL must run within the context of another application. The execution of the host application precedes the DLL. The host loads the DLL when needed.

You can specify the host application in the debug settings for the project. In the Debug pane of the Project properties window, select the Start External Program option as the Start action and enter the name of the hosting application. At the start of the debugging session, the specified host is executed and the DLL is loaded as needed. You can then debug the class library project.

If the host application and class library projects are in the same solution, setting the external program is not necessary. Simply set the host application to the startup project. Start a debugging session normally and debug both the host and class library projects.

When the host application is already running, attach the debugger to that process in the Attach To Process window. This process will initiate a debug session in which the class library project can then be debugged.

Debugging Setup

The Visual Studio debugger is fully configurable. Debugging sessions can be tailored to a developer or to the specific requirements of an application. For convenience, debug settings are saved in a configuration. Debug and Release are the default configurations and represent the most commonly used settings. The Debug configuration contains the project options for creating debug builds of an application. The Release configuration contains the options for release builds. Prior to changing project settings, it is good policy to confirm the active configuration. This avoids inadvertently changing the wrong configuration.

Debug and Release Configurations

As mentioned, the default configurations are Debug and Release. There are literally dozens of project options set in the predefined Debug and Release configurations. You can view and set debug configurations from Project properties in Solution Explorer. Open Project properties using the project context menu.

The important settings of the Debug configuration are as follows:

  • The DEBUG and TRACE constants—which control the behavior of the TraceSource, Debug, and Trace types—are defined.

  • The Debug Info option is set to full. This option requests the creation of a symbol file (pdb) whenever a project is built.

  • The output path is bin\debug.

  • The Optimize Code option is set to false, which disables code optimization.

The settings of the Release configuration are as follows:

  • The DEBUG constant is not defined; however, the TRACE constant is defined.

  • The Debug Info option is set to pdb-only.

  • The output path is bin\release.

  • The Optimize Code option is set to true, which enables code optimization.

Configuration Manager

Use the Configuration Manager to view, edit, and create configurations of project settings. To open the Configuration Manager, choose the Configuration Manager menu command from the Build Menu. Figure 12-3 shows the Configuration Manager. The Configuration Manager lists the current configuration of each project in the solution.

image from book
Figure 12-3: Configuration Manager

In the Configuration Manager, the Active Solution Configuration drop-down list selects a common configuration for every project. You can also create a new configuration from the drop-down list. In addition, you can rename or remove an existing configuration. The Active Solution Platform drop-down list selects the current platform.

Each row of the Configuration Manager represents a project. The project list has several columns of information:

  • Project This column shows the name of each project.

  • Configuration This column selects the current configuration of each project.

  • Platform This column selects the target platform of each project.

  • Build This column is an option box, which includes or excludes projects in builds of the solution.

Debug Settings

The Visual Studio environment, solution, and project maintain separate debug settings:

  • Debug settings of the Visual Studio environment are set in the Options dialog box. (From the Tools menu, choose the Options menu command.)

  • Debug settings of a project are set in the Project properties dialog box, which is opened by using the Project properties menu command on the Project menu.

  • Debug settings of a solution are set in the Solution properties window. Open this window from the context menu of the solution in Solution Explorer.

Visual Studio Environment Debug Settings

Debug settings for the Visual Studio product affect all projects. You can configure Edit And Continue, just-in-time (JIT) debugging, native debugging options, symbol servers, and general debugging options for the environment.

General window The general debugging options are an assortment of miscellaneous options. Each option can be enabled or disabled. Figure 12-4 shows the General window for setting these options.

image from book
Figure 12-4: The General window for setting general debugging options

Each option is described in Table 12-2.

Table 12-2: General Debugging Options

Option

Description

Default

Ask Before Deleting All Breakpoints

This option requests confirmation with the Delete All Breakpoints command.

Enabled

Break All Processes When One Process Breaks

This option interrupts all processes being debugged when a break occurs.

Enabled

Break When Exceptions Cross AppDomain Or Managed/Native Boundaries (Managed Only)

This option asks the Common Language Runtime (CLR) to catch exceptions that cross an application domain or cross between managed and native code.

Disabled

Enable Address-Level Debugging

This option allows certain operations at the address level, such as setting breakpoints on instruction addresses.

Enabled

Show Disassembly If Source Is Not Available

This option requests that the disassembly window be displayed when user source code is not available.

Disabled

Enable Breakpoint Filters

This option lets developers set filters on breakpoints, which can limit the scope of a breakpoint to a thread, process, or machine.

Enabled

Enable The Exception Assistant

This option automatically displays the Exception Assistant when an exception is raised while debugging.

Enabled

Unwind The Call Stack On Unhandled Exceptions

This option allows the user to unwind the call stack on a first chance exception, in which code changes can be made including repairing the source of the exception.

Enabled

Enable Just My Code (Managed Only)

This option allows the debugger to step into user code. Other code, such as system code, is skipped.

Enabled

Warn If No User Code On Launch

This option displays a warning when debugging is initiated if no user code is available.

Enabled

Enable Property Evaluation And Other Implicit Function Calls

This option evaluates the result of properties and other implicit function calls in Watch and Variables windows.

Enabled

Call ToString() On Objects In Variables Windows (C# Only)

This option calls ToString implicitly on objects displayed in variables windows.

Enabled

Enable Source Server Support

This option requests that Visual Studio get source course from the source code server (srcsrv.dll).

Disabled

Print Source Server Diagnostics Messages To The Output Window

This option displays messages from the source server in the Output window.

Disabled

Highlight Entire Source Line For Breakpoints And Current Statement

This option highlights an entire line of source code to emphasize the current breakpoint or statement.

Disabled

Require Source Files To Exactly Match The Original Version

This option asks the Visual Studio debugger to verify that the current source file matches the version built with the application.

Enabled

Redirect All Output Window Text To The Immediate Window

This option redirects messages from the Output window to the Immediate window.

Disabled

Show Raw Structure Of Objects In Variables Windows

This option disables debugger customizations, such as the DebuggerDisplay attribute.

Disabled

Suppress JIT optimizations On Module Load (Managed Only)

This option disables JIT optimizations, which can make code easier to debug.

Enabled

Warn If No Symbols On Launch (Native Only)

This option displays a warning when no symbol information is available.

Enabled

Edit And Continue window Edit And Continue permits changes to the source code while debugging. The changes are applied immediately without having to rebuild and restart the application. Edit And Continue is automatically enabled when breaking and then stepping through an application.

Most changes within a method are supported, such as deleting a line of code. However, Edit And Continue supports few changes that are made outside of a method. For example, deleting class members is not allowed. The following changes are not supported in Edit And Continue:

  • Modifying an active statement.

  • Surrounding an active statement with a foreach, using, or lock block.

  • If the active statement is in the current stack frame, you cannot surround the active statement with a catch or finally block. You also cannot add a nested exception handler beneath six levels.

  • If the active statement is not in the current stack frame, you cannot surround the active statement with a try, catch, or finally block. In addition, you cannot add a nested exception handler beneath six levels. Finally, you cannot change the code in the try block that contains the active statement.

  • Adding a new member.

  • Adding or changing a global symbol.

  • Changing the signature of a member.

  • Editing an anonymous method or a function containing a anonymous method.

  • Changing, adding, or deleting an attribute.

  • Changing or deleting a local variable.

  • Modifying a method that contains a yield return or yield break statement.

  • Changing, adding, or deleting a using directive.

  • Modifying a constructor that contains a local variable that is initialized in an anonymous method.

Edit And Continue is not available in the following environments:

  • Mixed-mode debugging

  • SQL debugging

  • Unhandled exceptions, unless the Unwind The Call Stack On Unhandled Exceptions general debugging option is enabled

  • Attached application debugging

  • 64-bit application debugging

  • After a failed rebuild of the current application

In the Edit And Continue window, the Edit And Continue feature can be enabled or disabled. Figure 12-5 shows the Edit And Continue window. The remaining settings pertain to native code.

image from book
Figure 12-5: Edit And Continue debug settings

Just-In-Time Debugging window The JIT debugger is attached to a running application when the program fails. Register Visual Studio as the JIT debugger in the Just-In-Time Debugging window. (See Figure 12-6.) Visual Studio can be the JIT debugger for Managed code, Native code, and Scripting. JIT debugging is discussed in the next chapter, "Advanced Debugging."

image from book
Figure 12-6: Just-In-Time Debugging window

Native window The Native window has two debugging options. Enable the Load DLL Exports option to load the export tables for DLLs. This is beneficial when the debug symbols are not available. For example, you can set breakpoints on functions exported from the library even though symbols are not available. The Enable RPC Debugging option enables stepping into Component Object Model (COM) remote procedure calls. Both options are disabled by default.

Symbols window Visual Studio can be configured to consult a symbol server and download the correct debugging symbols for the environment. Developers can use the Microsoft public symbol server or a local symbol server. Identify symbol servers and downstream paths in the Symbols window, which is shown in Figure 12-7. In the Symbol File Locations list box, symbol server paths can be entered or removed. Enter downstream servers in the Cache Symbols From Symbol Servers To This Directory Value.

image from book
Figure 12-7: Symbols window

Symbols can be loaded manually in the Modules window, as shown in Figure 12-8. Open the Modules window from the Debug menu and the Windows submenu. The Modules window lists the modules loaded in the current application. This window is applicable in break mode, not running mode. Each row of the window has the name of the module, path to the module, whether the module is optimized, source code availability, symbol status, and fully qualified path to the symbol file. The Symbol Status column indicates whether the symbols are loaded or skipped. Symbols are automatically skipped for optimized code. For skipped symbols, you can manually load the symbols for a specific module using the context menu. The Load Symbols menu command loads the symbols for the selected module. The Load Symbols and Symbol File columns are then updated. The Symbol Status column shows whether the symbols are successfully loaded for the module. The Symbol File column displays the path to the loaded symbol file. If the symbol file cannot be found for the specified module, you are prompted with the Find Symbols dialog box, as shown in Figure 12-9. From there, you can browse to the relevant symbol file.

image from book
Figure 12-8: Modules window

image from book
Figure 12-9: Find Symbols dialog box

From a module's context menu, the Symbol Load Information menu command displays general information about the symbols for the module. There is also a Symbol Setting menu command, which opens the Symbol debug window. You can configure symbols in this window.

Debug Settings for a Solution

Debug settings for a solution are set in the Solution property pages. The only settings available are Debug Source Files settings. (The Debug Source Files window is shown in Figure 12-10.) In this window, you can designate the directories that include and exclude source files.

image from book
Figure 12-10: Debug Source Files window

Debug Settings for a Project

Debug settings for a project set the startup action, command-line arguments, and other debug options. Figure 12-11 shows the Debug pane of the Project Property pages.

image from book
Figure 12-11: Debug pane of the Project Property pages

The Start Action option box indicates the startup action for debugging, which determines the application to launch when starting a debugging session. Here are the options:

  • The Start Project option, which is the default, starts the application of the startup project.

  • The Start External Program option is typically selected to debug class library projects. Specify the hosting application that loads the DLL of the class library project. You can then debug into the class library.

  • The Start Browser With URL option is useful for debugging Web applications. Browse to a URL that is controlled by the Web application. The Web application is then loaded via the Web server, which provides an opportunity to debug the Web application.

The Start Options option box contains the following miscellaneous settings:

  • Enter command-line arguments for the application in the Command Line Arguments text box.

  • The Working Directory text box sets the working directory of the application. This is the default path for directory- and file-related tasks.

  • The Use Remote Machine option names a remote machine, where the target application resides.

The Enable Debuggers option box indicates the kind of code that can be debugged:

  • The Enable Unmanaged Code Debugging option enables mixed-mode debugging. Developers can debug between managed and unmanaged code.

  • The Enable SQL Server Debugging option allows developers to debug CLR assemblies running within Microsoft SQL Server 2005.

  • The Enable The Visual Studio Hosting Process option activates the Visual Studio hosting process, which is a new feature in Visual Studio 2005.

Visual Studio Debugging User Interface

The user interface in Visual Studio 2005 has new debugging features, which include data tips and visualizers. These enhancements improve developers' debugging experience. Other features, such as tracepoints, represent improvements of existing features. Tracepoints are a new style of breakpoints.

Data Tips

While you are debugging, a data tip is displayed when the cursor is paused over a variable. Previously, data tips were helpful when viewing simple types, such as integers and other value types. However, the details of a complex type were not visible in a data tip. In Visual Studio 2005, data tips are improved and can display the fields of a complex type. The complex type is at first collapsed in the data tip. Expansion displays the type members and values in the data tip.

Visualizers

Visualizers display the underlying HTML, XML, or text associated with a data tip. For example, the improved data tip can display the composition of a dataset, which is a complex type. A dataset is more than the composition of all its members. Datasets are an abstraction of an XML data source. The ability to view the underlying data while debugging is invaluable. Imagine programmatically parsing XML data and receiving incorrect results. You need to know if the problem is in the XML or the program logic. The XML Visualizer provides a convenient way to view the source XML data.

Data tips now have an optional menu that displays available visualizers. When a data tip detects values that are compatible with a visualizer, the visualizer menu is automatically displayed.

In the following code, myxml is a field that contains XML. The XML defines an array of books.

 private string myxml = "<books><book><title>" +     "The Gourmet Microwave</title>" +     "<price>19.95</price>" +     "</book><book><title>Sushi, Anyone?</title>" +     "<price>49.99</price></book></books>"; 

The following code changes the content of the myxml field:

  string newbook="<book><title>" +      "Donis's Great Adventure</title>" +      "<price>9.95</price>" +      "</book>"; myxml=myxml.Insert(5, newbook); 

A string that contains XML, which describes another book, is added to the myxml string. A data tip can easily display the modified myxml string. Viewing myxml as XML provides additional clarification and important information. Move the cursor over the myxml variable, and a data tip appears. The modified string is displayed. From the data tip menu, select XML Visualizer. The XML Visualizer opens, as shown in Figure 12-12. The Visualizer uncovers a problem, which is that the underlying XML is not well-formed. Why not? The additional book was inserted at the wrong location in the myxml string, which is not detected from a simple data tip. However, the problem is caught by the Visualizer.

image from book
Figure 12-12: XML Visualizer uncovers a problem

The following code is modified to insert the book at the correct location in the myxml string. Now the XML Visualizer correctly displays the XML of the string. (See Figure 12-13.) You have just found and successfully resolved a bug!

image from book
Figure 12-13: XML Visualizer of myxml

 myxml=myxml.Insert(7, newbook); 

Visualizers are extensible; developers can create user-defined visualizers. The DataSet Visualizer is an example of a custom visualizer. It will display the data behind a dataset, which is very helpful. The following code displays rows in a dataset. A breakpoint is placed on the highlighted line.

 DataSet ds = new DataSet(); ds.ReadXml(@"C:\Xml\titles.xml"); DataRowCollection rows = ds.Tables[0].Rows; foreach (DataRow row in rows) {     lblData.Items.Add(row[0].ToString()); } 

The cursor is placed over the ds variable, which is an instance of a dataset. A data tip appears, which has several rows. There is a Visualizer menu on the row of the DataSet type. Choose the DataSet Visualizer from the menu. The dataset is then displayed in DataSet Visualizer window, with the results shown in Figure 12-14. From here, you can choose the data table to view, sort the data columns, and make basic changes to the fields.

image from book
Figure 12-14: Dataset Visualizer

Breakpoints

Breakpoints are stop signs in code where execution is interrupted. When a breakpoint is hit, the application enters the break mode. You can then debug the application with a variety of Visual Studio debugger commands. There are different kinds of breakpoints. A normal breakpoint appears as a red circle to the left of the target.

The F9 function key is the breakpoint command that sets a simple breakpoint on the selected line of source code. F9 is a toggle, which sets or clears a breakpoint. In addition, clicking in the leftmost dimmed column sets or clears a breakpoint. The Continue menu command (F5) on the Debug menu resumes an application. The application runs until another breakpoint is hit, the application is otherwise interrupted, or the debug session ends. Other commands, such as Run To Cursor, also resume execution.

You can add new breakpoints from the Debug menu and the New Breakpoint submenu. Break At Function adds a function breakpoint. New Data Breakpoint sets a breakpoint that breaks when a variable has changed. This option is not available in C# or Microsoft Visual Basic .NET.

Function Breakpoints

Function breakpoints break on the first line of a function and can be set at compile time or run time.

Set a function breakpoint from the New Breakpoint submenu. Choose the Break at Function command. Figure 12-15 shows the New Breakpoint dialog box. The shortcut key is Ctrl+B. In this example, a breakpoint is set on the first line of the WClass.MethodA function. As a shortcut, select the name of the target function first and open the New Breakpoint dialog box. The function name will automatically appear in the New Breakpoint dialog box. If you're breaking on an ambiguous or overloaded name, the Choose Breakpoints dialog box opens. For example, in the sample code, MethodA is ambiguous. There are several instances of MethodA in the application. In Figure 12-16, the New Breakpoint dialog box sets a breakpoint on MethodA. Figure 12-17 displays the Choose Breakpoints dialog box that subsequently appears, in which the user can select a specific location to set the breakpoint. To avoid ambiguity, you can enter the class name (Classname.Methodname) or the method signature.

image from book
Figure 12-15: New Breakpoint dialog box

image from book
Figure 12-16: Setting a breakpoint on MethodA

image from book
Figure 12-17: Choose Breakpoints dialog box

The Use IntelliSense To Verify The Function Name option of the New Breakpoint dialog box requests the Choose Breakpoint dialog box whenever a user enters an ambiguous or overloaded function name. With this option, users are notified of invalid function names. With the option disabled, there is no notification, and the New Breakpoint dialog box simply closes without setting the breakpoint.

You can also set breakpoints in the Call Stack window, which is available on the Debug menu within the Windows submenu. A function breakpoint on the call stack breaks upon re-entering that method as the stack unwinds. For example, MethodA calls MethodB. MethodB then calls MethodC. A breakpoint is then set on MethodA in the Call Stack window. When MethodB returns and the related stack frame is removed, the application is interrupted on re-entering MethodA. The Call Stack window is available in break mode. Set a breakpoint for a specific method using the context menu or the F9 breakpoint keyboard shortcut. A breakpoint set on MethodA in the call stack is exhibited in Figure 12-18.

image from book
Figure 12-18: Breakpoint in the Call Stack window

Breakpoints Window

Developers can manage breakpoints in the Breakpoints window (opened from the Debug menu and the Windows submenu). Figure 12-19 shows the Breakpoints window.

image from book
Figure 12-19: Breakpoints window

In the Breakpoints window, breakpoints are shown on separate rows. The first column of each row is the enabled/disabled options box. If a breakpoint is enabled, the option box is checked. Uncheck the column to disable the breakpoint. The next column provides the description and location of the breakpoint. The Condition column shows any conditions set on the breakpoint. The final column shows the hit count. In that column, Break Always means that the program is interrupted every time the breakpoint is hit.

The context menu has several valuable options that affect the selected breakpoint. The first menu command is Delete, which removes the breakpoint. The Go To Source Code menu command redirects to the breakpoint in the source code. The Go To Disassembly menu command redirects to the disassembly at the breakpoint. For this command, the application must be in break mode. The remaining commands of the context menu customize the selected breakpoint and are explained in the following sections.

Location This command changes the location of a breakpoint. You are presented with the Address Breakpoint or File Breakpoint dialog box (shown in Figures 12-20 and 12-21). The Address Breakpoint dialog box is displayed for memory breakpoints, such as a breakpoint set on the call stack. The File breakpoint dialog box is displayed for line breakpoints in source code.

image from book
Figure 12-20: Address Breakpoint Dialog Box

image from book
Figure 12-21: File Breakpoint Dialog Box

Condition This command sets additional criteria for honoring a breakpoint. The condition can be a Boolean expression. If true, the breakpoint is honored. Otherwise, the breakpoint is ignored. The condition can also be based on changes to a value. If the value is changed, the breakpoint is honored.

Look at the following sample code. The breakpoint is set on the dimmed line. We want to honor the breakpoint only when ivalue contains an even value.

 int[] numbers ={1,2,3,4,5,6,7,8,9,10,     11,12,13,14,15,165,17,18,18,20}; int total = 0; foreach (int ivalue in numbers) {     total += ivalue; } 

The Breakpoint Condition dialog box, shown in Figure 12-22, sets the condition to break on even values.

image from book
Figure 12-22: Conditional Breakpoint dialog box

Hit Count This command honors a breakpoint based on the hit count. Table 12-3 lists the hit count options.

Table 12-3: Hit Count Options

Option

Description

Break Always

This option always honors the breakpoint.

Break When The Hit Count Is Equal To

This option honors the breakpoint when that instance of the breakpoint occurs. For example, interrupt on the fourth occasion of a specific breakpoint.

Break When The Hit Count Is A Multiple Of

This option honors the breakpoint as a multiple of a value, for example, every third instance of the breakpoint.

Break When The Hit Count Is Greater Than Or Equal To

This option honors the breakpoint starting at a certain instance; for example, honor the breakpoint at the third and every successive instance.

The Breakpoint Hit Count dialog box, shown in Figure 12-23, honors every third instance of the selected breakpoint.

image from book
Figure 12-23: Breakpoint Hit Count dialog box

Filter This command sets the affinity of a breakpoint to a machine, thread, or process. The breakpoint interrupts only in the context of the stated filter. Table 12-4 lists the available contexts.

Table 12-4: Filter Contexts

Context

Description

MachineName

This context is the name of the machine. Breakpoint is honored if the process is running on that machine.

ProcessId

This context is the process identifier. Breakpoint is honored if code is executing in the specified process.

ProcessName

This context is the process name. Functionally equivalent to the ProcessId context.

ThreadId

This context is the thread identifier. Breakpoint is honored if code is executing on a specific thread.

ThreadName

This context is the thread name. Functionally equivalent to the ThreadId context.

The following code creates two threads that asynchronously execute the same method (MethodA). The threads are named FirstThread and SecondThread. A breakpoint is set on the highlighted line in MethodA. As a standard breakpoint, both threads are interrupted—probably alternating between the threads.

 private void btnFilter_Click(object sender, EventArgs e) {     Thread t1=new Thread(new         ThreadStart(MethodA));     t1.Name="FirstThread";     t1.IsBackground=false;     Thread t2=new Thread(new         ThreadStart(MethodA));     t2.Name="SecondThread";     t2.IsBackground = false;     t1.Start();     t2.Start(); } public void MethodA() {     while (true)     {         Thread.Sleep(3000);     } } 

To break on the first thread alone, open the Breakpoint Filter dialog box, shown in Figure 12-24. Specify FirstThread as the thread name. Future instances of the breakpoint interrupt the first thread, but not the second thread. You can confirm the results in the Threads window.

image from book
Figure 12-24: Breakpoint Filter dialog box

When Hit This command creates a tracepoint (discussed in the next section).

The Breakpoints window toolbar, shown in Figure 12-25, offers several shortcuts. The controls on the toolbar are New, Delete, Delete All Breakpoints, Disable All Breakpoints, Go To Source Code, Go To Disassembly, and Columns. The New drop-down list inserts a new function or data breakpoint. Data breakpoints are not available in managed code. The Columns button customizes the content of the Breakpoints window, in which developers can add or remove columns of information.

image from book
Figure 12-25: Breakpoints window toolbar

Tracepoints

Tracepoints assign an operation to a breakpoint. You can assign source code or a macro to a breakpoint. When the breakpoint is hit, the source code or macro is executed. Before tracepoints, this process required two steps: setting a breakpoint and inserting temporary code into source file. When the breakpoint was no longer needed, the developer had to remember to remove the temporary code.

There are two methods for setting a tracepoint. You can open the context menu on the source line in which the tracepoint should be inserted. Select the Breakpoint submenu and the Insert Tracepoint menu command. Alternatively, you can open the context menu on a source line that already contains a breakpoint. From the Breakpoint submenu, choose the When Hit menu command. Either approach opens the When Breakpoint Is Hit dialog box, in which a tracepoint is defined. (See Figure 12-26.)

image from book
Figure 12-26: When Breakpoint Is Hit dialog box

In the Print A Message edit box, enter a display string for the output window. Expressions are entered within curly braces: {expression}. Special keywords can be used, such as $ADDRESS, $TID, and $FUNCTION. In the Run A Macro edit box, enter the name of a macro. This macro is run when the breakpoint is hit. The Continue Execution option sets a soft breakpoint. When the breakpoint is hit, soft breakpoints do not interrupt the application; they execute the designated code or macro. Soft breakpoints appear as diamonds in the source code editor.

The statement in the following When Breakpoint Is Hit dialog box (shown in Figure 12-27) adds two local variables and displays the results in the output window.

image from book
Figure 12-27: Print a message selected in the When Breakpoint Is Hit dialog box

From the preceding tracepoint, the following code is displayed in the Output window:

 Function: WindowsApplication1.WClass.MethodA() [5 + 10 = 15] 

Table 12-5 lists the special keywords that can be used in the Print a message statement.

Table 12-5: Tracepoint Keywords

Keyword

Description

$ADDRESS

This keyword returns the address of the current instruction.

$CALLER

This keyword returns the name of the previous function on the call stack, which is the calling function.

$CALLSTACK

This keyword returns the call stack.

$FUNCTION

This keyword returns the name of the current function.

$PID

This keyword returns the process identifier of the current process.

$PNAME

This keyword returns the name of the current process.

$TID

This keyword returns the thread identifier of the current thread.

$TNAME

This keyword returns the name of the current thread.

Breakpoint Symbols

Breakpoints are annotated with icons in both the source code and the Breakpoints window. Each category of breakpoint has a different symbol, as described in Table 12-6.

Table 12-6: Breakpoint Symbols

Symbol

Description

Filled circle

This icon signifies a normal breakpoint, such as a function or location breakpoint.

Diamond

This icon signifies a tracepoint that has the Continue execution option enabled.

Filled circle with a plus +

This icon signifies a filter breakpoint. Filter breakpoints include the Condition, Hit Count, and Filter breakpoints.

Hollowed circle

This icon signifies a disabled breakpoint.

Code Stepping

Stepping through source code is the most common action in a debugging session. Step commands execute an application in source line increments. With each step, execution continues incrementally. Between steps, expressions can be evaluated, variables updated, functions called, and scope changed. Debug windows are updated at each step to reflect any changes that occurred during the partial execution. This sometimes requires refreshing. Extensive monitoring and expressions in debug windows can precipitously hurt performance.

Step Commands

There are several step commands, described as follows:

  • Step Into This command steps to the next source line. If that is a function call, the debugger steps into the function. You can then step through that function. For nested function calls, the Step Into command steps into the innermost functions first.

  • Step Over This command steps to the next source line. However, it will not step into a function call. The function call is treated as a single line of source code.

  • Step Out This command executes the remainder of the current function. Execution is then interrupted at the first source line after the call site.

  • Set The Next Statement This command lets developers change the next statement, which is useful for skipping one or more lines of source code. In the source editor, the current line is highlighted with a yellow arrow. This is the next statement to execute. When the cursor hovers over the yellow arrow, it changes into an arrow itself. You can then drag the current source line up or down. The next step selected must be within the scope of the current source line. For example, dragging the next step to another function is illegal. This command is not available during JIT debugging. It is also not available when debugging a StackOverflowException or ThreadAbortException exception.

Set The Next Statement Walkthrough

This walkthrough demonstrates the Next Statement command. In the sample application, variables locala and localb are initialized in MethodA. They are then incremented. The SwitchValues method is called to swap the values of the local variables. The parameters are passed by reference. After the method call, locala is 11 and localb is 6. This is the code:

 public void MethodA() {     int locala = 5, localb = 10;     ++locala;     ++localb;     SwitchValues(ref locala, ref localb);     MessageBox.Show(locala.ToString());     MessageBox.Show(localb.ToString()); } public void SwitchValues(ref int param1,     ref int param2) {     int temp = param1;     param1 = param2;     param2 = temp; } 

These are the steps of the walkthrough that uses the previous code:

  1. Set a breakpoint on the source line where locala is incremented. When the breakpoint is hit, the current line is marked with a yellow arrow. The breakpoint and the current source line are initially at the same location, as shown in Figure 12-28.

  2. Drag the yellow highlight for the current line down to the first MessageBox.Show statement, which jumps past the statements that increment the local variables and the SwitchValues method call. Therefore, the values are neither incremented nor swapped. Figure 12-29 shows the repositioned current line.

  3. Continue execution, and the values for locala and localb are displayed. The locala variable is 5, whereas localb is 10. Otherwise, the values would have been 11 and 6, respectively.

image from book
Figure 12-28: Breakpoint and current line

image from book
Figure 12-29: Repositioned current line

Debug Toolbar

The Debug toolbar contains shortcuts to several debugging commands, including the step commands. (See Figure 12-30.) Some of the buttons and commands are enabled only in break mode.

image from book
Figure 12-30: Debug toolbar

The first set of buttons on the toolbar includes the Start Debugging, Break All, Stop Debugging, and Restart buttons. The next set of buttons includes the Show Next Statement, Step Into, Step Over, and Step Out buttons. The Show Next Statement button redirects to the next statement in the code editor. This statement is highlighted with a yellow arrow. Next is the Hexadecimal button, which changes the values in the debug windows from decimal to hexadecimal. The final button is a drop-down list that displays a menu of debug commands.

Debug Windows

To assist in debugging, Visual Studio 2005 offers a myriad of debug windows, described in the following sections.

Breakpoints Window

Manage breakpoints in the Breakpoints window. In this window, you can insert, delete, and disable breakpoints. (The Breakpoints window was shown and discussed in detail earlier in this chapter.)

Output Window

This window contains messages from various sources in Visual Studio. The Output window is also available from the View menu. The Output window is displayed in Figure 12-31.

image from book
Figure 12-31: Output window

The Output window has a toolbar, which has several buttons. Output From is the first button on the toolbar. It filters the message sources, such a Build and Debug messages. The next three buttons locate build errors in the code editor: Find Message in Code, Go to Previous Message, and Go to Next Message. The next button, Clear All, erases the content of the Output window. The Toggle Word Wrap button toggles word wrap in the Output window.

Script Explorer Window

ASP.NET Web applications have client- and server-side code. Client-side code is written in either JavaScript or VBScript. In the Script Explorer window, developers can debug client-side scripts, including setting breakpoints. The window lists script files and their scripts. The Script Explorer window is shown in Figure 12-32. In the sample Script Explorer window, the scripts of the default.aspx page are listed. The anonymous scripts are usually scripts associated with for server-side controls, such as validation controls.

image from book
Figure 12-32: Script Explorer window

To use the Script Explorer window, you must enable Script debugging in Microsoft Internet Explorer. The Disable Script Debugging (Internet Explorer) option is enabled by default in Internet Explorer. This option must be disabled to allow script debugging. Open the Tools menu and select the Internet Options menu command. On the Advanced tab, disable the Disable Script Debugging (Internet Explorer) option.

Watch Window

You can watch variables and expressions in a Watch window. The Watch, Locals, Auto, and QuickWatch windows are considered variables windows—they have the same general user interface and functionality. The variables windows are disabled in running mode; you must be in break mode to use them. A Watch window, like other variables windows, has three columns. The Name column has the variable name or expression to evaluate. The Value column displays the variable value or result of the expression. The Type column is the data type of the variable or expression. There are four Watch windows for grouping related values.

Variable values can be modified directly in a Watch window. Changed values are highlighted in red. This is an exceptional way to test applications with values that stress the program. You can also test how the application handles errant conditions.

You can add variables and expressions in a Watch window directly. The QuickWatch dialog box is an expedited means of inspecting a value or expression and optionally adding that item to a permanent Watch window. The QuickWatch dialog box has an Add Watch button. If desired, you can enter new expressions in the Expression text box. The QuickWatch window is available on the Debug menu. Shortcuts for QuickWatch are Ctrl+Alt+Q or Shift+F9. Before opening the QuickWatch window, select the target variable in the source editor.

Expressions Debugger expressions are expressions entered in the Watch, Quickwatch, Breakpoints, or Immediate window. These expressions are evaluated by the Managed Expression Evaluator of the Visual Studio debugger. Use debugger expressions to calculate values or to call methods. IntelliSense is available when entering debugger expressions in a debug window or visualizer. The added flexibility of doing more than examining static data is invaluable.

Debugger expressions are evaluated similarly to regular expressions. There are some unique idiosyncrasies of the Managed Expression Evaluation.

  • It ignores access modifiers.

  • All members, regardless of accessibility, are available.

  • Expressions are evaluated in an implicitly unsecure context.

  • Checked blocks are ignored and evaluated as unchecked.

  • Anonymous methods are not supported in debugger expressions.

Expressions can contain constants, function calls, and identifiers within scope, such as locals, parameters, and fields. Most operators, such as +, -, ==, !=, ++, --, and / are available. You can even use the typeof and sizeof operators. The this reference is supported. In addition, simple casts are allowed.

Expressions are evaluated between every step command. Refresh the expression to view any updates. Beware of side effects, which can alter the state or execution of the programming unknowingly. For example, calling a function in an expression that changes the state or outcome of the application may cause adverse side effects.

Expression walkthrough The following steps list a walkthrough of expressions. A breakpoint is set on the Form_Load event in the Expressions application. Run the application until the breakpoint is hit.

  1. Open a Watch window from the Debug menu. The Form_Load method has a sender and an e parameter. (You can view their values in the Watch window.) This Watch window, including the two parameters, is shown in Figure 12-33.

  2. The Sample application has a ZClass, which is relatively simple:

     class ZClass {     private int fielda = 5;      private int fieldb = 10;     public int MethodA()     {         int locala = 5;         locala = 12;         return fielda + fieldb;     } } 

  3. A breakpoint is set on the highlighted line in MethodA. When the breakpoint is hit, add fielda, fieldb, locala, and localb to the Watch window. Click the Test button to hit the breakpoint. The localb variable is not declared in the function. For that reason, it is displayed with an exclamation symbol. Enter the ++fielda expression. Figure 12-34 shows the new Watch window.

  4. Step two individual statements. (Press F10 twice.) Click the Refresh button to update the value of the ++fielda expression. (The Refresh button is the green swirling button to the right of the field.)

  5. The DumpType class has a static method, which dumps the methods and parameters of a type. The DumpType.GetReflection method returns a string that contains the methods of a type. This function is not called elsewhere in the program. It is created purely for debugging reasons. Enter the DumpType.GetReflection in the expression. Pass the type of the current object as the sole parameter. (See Figure 12-35.) You view the returning value of GetReflection with the Text Visualizer. The results are shown in Figure 12-36.

image from book
Figure 12-33: Watch window with two parameters

image from book
Figure 12-34: Watch window with locala, localb, fielda, and fieldb values

image from book
Figure 12-35: DumpType.GetReflection expression in the Watch window

image from book
Figure 12-36: Results in the Text Visualizer

Autos Window

The Autos window lists the values from the current and preceding line of code, which includes this reference. The items of the Autos window are populated by the Visual Studio debugger, not the developer. You can change the values in the window, which are then highlighted in red. Figure 12-37 shows the Autos window.

image from book
Figure 12-37: Autos window

Locals Window

The Locals window lists the local variables that are currently in scope. Otherwise, the functionality is identical to the Autos window.

Immediate Window

The Immediate window is the command-line version of the Visual Studio debugger. You can enter print values, evaluate expressions, perform menu commands, execute statements, and more. This can be done without having to change the application. Command windows are either in immediate or command mode. Command mode is preferred for executing one or more Visual Studio commands. These are menu and other commands.

For evaluating expressions, inspecting values, and otherwise debugging an application, immediate mode is preferred. The default is the command mode. When in immediate mode, switch to command mode temporarily by prefixing commands with a greater than (>) symbol. From the Immediate window, the cmd command switches to command mode in the Command window. Switch back to immediate mode and the Immediate window with the immed command.

You can navigate the Immediate window with arrows. Table 12-7 shows the various ways to navigate the window.

Table 12-7: Navigating the Immediate Window

Keystroke

Description

Up arrow on command line

Previous command.

Down arrow on command line

Next command.

Ctrl+Up Arrow or Up arrow in Command window

Up window.

Ctrl+Down Arrow or Down arrow in Command window

Down window.

Esc

Transfer focus to code editor. If not at the last command in the Immediate window, Esc is required.

There are limitations to the tasks that can be performed in the Immediate window. For example, you cannot declare new variables, define a label, or create an instance of an object. Special commands such as the K command are not available in managed-only code. Therefore, special commands for the Immediate window are not explored here. In addition, the Immediate window does not accept goto, return, and loop statements, or any statement requiring transfer of control.

Commands can have arguments and options. For example, the FindinFiles command has both. IntelliSense will present the options, which are preceded with a forward slash. You must type a forward slash for IntelliSense to display the available options. The following code is an example of the Edit.FindinFiles command. If the command is represented by a window in the user interface, consult that window for an understanding of options and arguments. In the window, edit text boxes are usually interpreted as arguments, and everything else becomes an option. Figure 12-38 shows the Find In Files dialog box, which has one text box and several options. The text box is the single argument of the FindinFiles command. You can use as many options as needed. The defaults are defined in the window. In the example, the FindinFiles command is used with two options: The first option requests a case-sensitive search, and the second option directs the command to search all files in the root directory. This command has a single argument, which is the search text.

image from book
Figure 12-38: Find in Files window

 >Edit.FindinFiles /case /lookin:"c:\*" donis 

Aliases are short forms of debugging commands. They are a convenience and never required. Table 12-8 lists some useful aliases.

Table 12-8: Command Aliases

Alias

Command

?

Debug.Print

bl

Debug.Breakpoints

callstack

Debug.CallStack

cls

Debug.ClearAll

cmd

View.CommandWindow

du

Dump Unicode

g

Debug.Start

immed

Tools.ImmediateMode

K

Debug.ListCallStack

locals

Debug.Locals

memory1

Debug.Memory1

memory2

Debug.Memory2

P

Debug.StepOver

pr

Debug.StepOut

print

File.Print

q

Debug.StopDebugging

rtc

Debug.RunToCursor

saveall

File.SaveAll

T

Step.Into

threads

Debug.Threads

~

Debug.ListThreads

~*k

Debug.ListCallStack /AllThreads

Developers might want to create user-defined aliases for frequently performed tasks. The alias command defines a new alias. The following command defines two new aliases. The pripro alias replaces all instances of the private with the protected keyword in the current source file. The propri alias performs the reverse operation.

 >alias pripro edit.replace /all private protected >alias propri edit.replace /all protected private 

Call Stack Window

The Call Stack window shows the functions currently on the stack. The current function is highlighted with a yellow arrow. By default, the window has two columns: the name of the function and the source language. The Name column combines several fields: the module name, function name, parameter information, line number, and byte offset. Parameter information includes parameter type, parameter name, and parameter values. Use the context menu to customize the display. Except for the function name, everything is optional. Figure 12-39 shows the Call Stack window, which is available only in break mode.

image from book
Figure 12-39: Call Stack window

The call stack does not include external code. In some circumstances, information on external functions in the call stack can be helpful. To view external code, disable the Enable Just My Code (Managed Only) option in the Options dialog box of the Tools menu under the Debugging settings in the General node. Figure 12-40 shows the Call Stack window with this option disabled. For clarity, extraneous information is removed from the Name column. You can now trace the call stack into System functions to obtain what may be invaluable information.

image from book
Figure 12-40: Call Stack window with Enable Just My Code disabled

Each row of the call stack represents a function and a stack frame. Some variables windows are based on the current stack frame. For example, the Autos and Locals windows are from the current stack frame. However, you might want to view similar information about the other stack frame. To switch stack frames, position the cursor on the target frame. Open the context menu in the call stack and choose the Switch To Frame menu command. A curved green arrow appears next to the updated current stack frame. Variable windows are updated according to the change.

Another option on the Call Stack context menu is the Include Calls From/To Other Threads menu command. This command follows the call stack through thread calls. For example, this is helpful when tracing the call stack from an XML Web Service into an ASP.NET application.

Function breakpoints and tracepoints are settable in the Call Stack window. You can use shortcut keys, such as F9, or you can use the context menu.

Threads Window

The Threads window presents the active threads of the current process. This window is available only in break mode. A thread is a single path of execution in a process. Threads own assets, such as local variables, thread context, and thread local storage.

The Thread application is provided to create threads. You can assign a name and priority to each new thread. Each thread executes the sample MethodA function. When you run the Thread application in a debugging session, thread T1, T2, and T3 are created of varying priorities. Then the BreakAll command is issued from the Debug menu. Figure 12-41 shows the Threads window for the application at that moment.

image from book
Figure 12-41: Threads window

The columns of the Threads window are Thread Identifier, Thread Name, Method Location, Thread Priority, and Suspend Count. From the context menu, suspend a thread with the Freeze menu command. Suspended threads are assigned a double-bar icon. Choose the Thaw menu command to resume the thread. Forcibly suspending a thread can affect the normal operation of an application. The Switch To Thread menu command of the context menu changes the current thread. You can also double-click the target thread to switch the current thread. Some windows are based on the current thread, such as the Call Stack. These windows are updated when the current thread is changed.

Modules Window

The Modules window lists the modules, executables, and DLLs, which are loaded into the application. There is considerable information presented on each module, as described in Table 12-9.

Table 12-9: Modules Window Columns

Column

Description

Name

This column is the name of the module.

Path

This column presents the fully qualified path to the module.

Optimized

This column indicates whether the module is optimized during JIT compilation. The current module being debugged is likely not optimized. Debug versions of modules are generally not optimized.

User Code

This column indicates whether user code is available for a particular module.

Symbol Status

This column indicates whether symbols are loaded for this module. It also indicates if exports are only loaded.

Symbol File

When loaded, this column displays the fully qualified path and name of the symbol file.

Order

This column lists the load order of modules.

Version

This column provides the version number of the module.

Timestamp

This column is the timestamp when the module was created.

Address

This column is the start and end load address of the module.

Process

This column is the process identifier of the application that hosts the module.

From the context menu, you can request additional information on the symbol file. You can also visit Symbol settings in Project options, where symbols are managed.

Processes Window

The Processes window enumerates the processes being debugged by the Visual Studio debugger, including attached processes. Table 12-10 describes each column of the window.

Table 12-10: Processes Window Columns

Column

Description

Name

This column is the name of the process.

ID

This column is the process identifier of the process.

Path

This column is the path to the executable.

Title

This column contains the title of application. For a Windows Forms application, this is the content of the title bar.

State

This column shows the state of the application, such as the break or running state.

Debugging

This column indicates the type of debugging, such as managed or native debugging.

Transport

This column is the transport to the application. The default is no authentication.

Transport Qualifier

This column is the machine name.

Memory Window

The Memory window dumps the memory of the current process. (See Figure 12-42.) Other windows format memory for specific purposes. For example, the Locals window displays the memory of local variables alone. The Memory window provides an unfiltered and raw presentation of process memory. Four memory windows provide the opportunity to maintain different views of the process. Memory windows are available in break mode and only if the Enable Address-Level Debugging Option is enabled. Set this option within Debug settings in the General node. Open from the Tools menu and the Options command.

image from book
Figure 12-42: Memory window

Enter a memory address in the Address edit box. You can enter a memory address directly or by using a drag-and-drop operation. The Memory window displays rows of data at memory addresses. Column 1 of the Memory window is the starting address of a row of data. The final column is the text translation. The memory dump is in the intervening columns.

The Memory window can be formatted using the context menu. You can change the size of the data columns to 1-, 2-, 4-, or 8-byte columns. The display can also be changed to 32- or 64-bit data presentation. The text translation can be formatted as ANSI, Unicode, or hidden.

Disassembly Window

The Disassembly window shows the native assembly of the application, which is the native code generated by JIT compilation. By default, if available, the source code is displayed also. Each assembly instruction is displayed in several columns: the instruction address, mnemonic, and parameters. (See Figure 12-43.) Use the context menu to change the format of the Disassembly window.

image from book
Figure 12-43: Disassembly window

Registers Window

The Registers window displays the state of the registers. Assembly-level programming typically relies heavily on registers. For this reason, the Disassembly and Registers windows are often used together. The Registers window is shown in Figure 12-44. Use the context menu to change the format of the display.

image from book
Figure 12-44: Registers window

Table 12-11 describes the common registers.

Table 12-11: Registers

Register

Description

EAX

This register is a general-purpose register. It is commonly used as the destination of a math operation.

EBX

This register is a general-purpose register.

ECX

This register is a general-purpose register and is commonly used for counting.

EDX

This register is a general-purpose register.

EIP

This register contains the next instruction pointer.

ESP

This register contains the pointer to the top of the stack.

ESI

This register is the source index. The ESI and EDI registers are frequently used in string operations.

EDI

This register is the destination index. The ESI and EDI registers are frequently used in string operations.

EBP

This register contains the base pointer of the current stack frame.

Tracing

Tracing instruments an application through trace messages sent during program execution. Trace messages are used for a variety of reasons. They can confirm the execution sequence of a program, which is useful for assuring that program flow is correct (often the cause of bugs). Trace messages can display the state of the application at different stages. You can track the state of objects, local variables, or other data throughout the lifetime of an application. Finally, trace messages are used to track events, such as start, stop, and user-defined events. Tracing replaces the old technique of monitoring an application via Console.WriteLine statements.

In the Microsoft .NET Framework 2.0, TraceSource is the preferred type for tracing. It replaces the Debug and Trace classes, which remain available as legacy types. The TraceSource class is in the System.Diagnostics namespace. To enable tracing, the TRACE symbol must be defined in the application. In the source code, this is accomplished with the #define statement. Alternatively, define the symbol while compiling the program. The /d compiler option defines a symbol:

 csc /d:TRACE test.cs 

Table 12-12 explains the important members of the TraceSource class.

Table 12-12: TraceSource Methods

Level

Description

Constructors

Both constructors assign a name to the TraceSource object. The two-argument constructor also sets a default severity level for all trace messages originating from this instance.

TraceSource(string name)

TraceSource(string name, SourceLevels defaultLevel)

Close

This method closes all the trace listeners in the Listeners collection.

void Close()

Flush

This method flushes the trace listeners in the Listeners collection. This ensures that cached messages are reported.

void Flush()

TraceData

This method creates a trace message consisting of event type, a trace identifier, and any trace data. The event type is the severity level.

 [ConditionalAttribute("TRACE")] public void TraceData(        TraceEventType eventType,        int id,        Object data) [ConditionalAttribute("TRACE")] public void TraceData(        TraceEventType eventType,        int id,        Object [] data) 

TraceEvent

This method creates a trace message consisting of an event type and message, which is written to the Listeners collection. The first overload—the method without the message parameter—creates a blank trace message.

 [ConditionalAttribute("TRACE")] public void TraceEvent(        TraceEventType eventType,        int id) [ConditionalAttribute("TRACE")] public void TraceEvent(        TraceEventType eventType,        int id,        string message) [ConditionalAttribute("TRACE")] public void TraceEvent(        TraceEventType eventType,        int id,        object [] data) 

TraceInformation

This method creates an informational message, which is written to the Listeners collection.

 [ConditionalAttribute("TRACE")] public void TraceInformation(        string message) [ConditionalAttribute("TRACE")] public void TraceInformation(        string format,        params Object[] args) 

TraceTransfer

This method creates transfer messages, which are written to the Listeners collection.

 [ConditionalAttribute("TRACE")] public void TraceTransfer(        int id,        string message,        Guid relatedActivityID) 

The TraceSource type also has several valuable properties. Table 12-13 lists the properties.

Table 12-13: TraceSource Properties

Property

Type

Attributes

This property gets the custom switch attributes that are defined in the application configuration file.

StringDictionary

Listeners

This property gets an array of listeners. Listeners are the targets of trace messages.

TraceListenerCollection

Name

This property gets the name of the TraceSource object. This name is most likely used with the SourceFilter property of a listener.

string

Switch

This property gets or sets the trace switch associated with this TraceSource object. This filters the trace messages sent from the TraceSource object.

SourceSwitch

Tracing is based on criteria including severity levels, switches, listeners, and listener filters. This determines when, where, and how trace messages are reported. TraceSource instances create trace messages. Severity levels are assigned to trace messages, which set the message importance. Switches filter trace messages based on their severity levels. Listeners receive trace messages. Finally, listener filters control the trace messages reported from a listener. Tracing is configurable either programmatically or using an application configuration file. The application configuration file is recommended. With the application configuration file, tracing is controlled, enabled, or disabled without recompiling the program. For a production application, this is essential. To introduce tracing concepts, the programmatic approach is presented first.

Trace messages are set to severity levels. The trace levels determine the importance or urgency of a message. Some trace messages are activity related, such as the start and stop time of an application. Informational trace messages are used to stub methods and check program flow. Tracing the value of an invalid parameter, which results in an exception, should be higher priority. TraceEventType enumeration defines the trace levels. Table 12-14 enumerates the trace levels. The levels are listed in order of highest to lowest priority.

Table 12-14: Trace Levels

Level

Description

TraceEventType.Critical

This error level is reserved for conditions that destabilize an application or are fatal.

TraceEventType.Error

This error level is reserved for conditions that destabilize an application but are recoverable.

TraceEventType.Warning

This error level is reserved for noncritical conditions.

TraceEventType.Information

This error level is reserved for general information that might aid in the diagnosing of an error condition.

TraceEventType.Verbose

This error level is reserved for general information not necessarily associated with an error condition.

TraceEventType activities

See Table 12-14.

Some trace levels are related to activities, which are linked to events. The activity traces are grouped at the same trace level, which is the lowest trace level, beneath the Verbose level. Table 12-15 shows the list of activity traces.

Table 12-15: Activity Traces

Activity

Description

TraceEventType.Start

This activity indicates that an operation is starting.

TraceEventType.Stop

This activity indicates that an operation is stopping.

TraceEventType.Suspend

This activity indicates that an operation is suspending.

TraceEventType.Resume

This activity indicates that an operation is resuming.

TraceEventType.Transfer

This activity indicates a change of correlation identity.

Switches filter trace messages based on trace levels, which determine which messages a TraceSource sends. With switches, developers can selectively enable or disable certain trace messages. If an application is crashing, you might decide to enable critical and error trace messages. When algorithms are performing incorrectly, you might want to enable general and activity traces. Avoiding unnecessary tracing improves performance. Tracing can generate a voluminous amount of information. A filter reduces the volume of trace messages, which conserves resources. Switch filters are defined in the SourceSwitch class. The SourceSwitch class has the Level property, which is a SourceLevels type. SourceLevels is the enumeration that defines the trace messages that are filtered. This is a bitwise flag, and the various SourceLevels can be combined. The source levels are detailed in Table 12-16.

Table 12-16: Source Level

Filter

Description

SourceLevels.ActivityTracing

This level forwards activity trace messages. Other messages are filtered.

SourceLevels.All

This level forwards all trace messages.

SourceLevels.Critical

This level forwards only critical trace messages.

SourceLevels.Error

This level sends error and critical trace messages. Other trace levels are filtered.

SourceLevels.Information

This level filters verbose and activity trace messages. Other trace messages are sent.

SourceLevels.Off

This level filters all trace messages.

SourceLevels.Verbose

This level filters activity trace messages. All other messages are sent.

SourceLevels.Warning

This level filters all trace messages except for Critical, Error, and Warning events.

To set the filter, assign an instance of SourceSwitch to the TraceSource.Switch property. The SourceSwitch has two constructors:

  • public SourceSwitch(string name)

  • public SourceSwitch(string name, string defaultLevel)

  • SourceSwitch also has several useful properties, which are listed in Table 12-17.

Table 12-17: SourceSwitch Properties

Property

Type

Attributes

This property gets the custom attributes that are defined in the application configuration file.

StringDictionary

Description

This property gets a general description of the switch. It defaults to an empty string.

string

DisplayName

This property gets the name of the switch.

string

Level

This property gets or sets the level of the switch.

SourceLevels

The following code creates an instance of TraceSource and TraceSwitch. The switch is then assigned to the TraceSource.

 TraceSource ts=new TraceSource("sample"); SourceSwitch sw=new SourceSwitch("switch"); sw.Level=SourceLevels.All; ts.Switch=sw; 

The event type of trace messages and the switch source level combine to determine which messages are sent. The following code sends trace messages in each event type: critical, error, information, and so on. The switch level, which is case sensitive, is read from the command line and determines which messages are recognized. A console listener is used in the application, which displays the unfiltered trace messages in a console window. This is an excellent program for understanding the combination of trace event types and switch levels.

 #define TRACE using System; using System.Diagnostics; namespace Donis.CSharpBook{     public class Starter{         public static void Main(string [] argv){            TraceSource ts=new TraceSource("sample");            SourceSwitch sw=new SourceSwitch("switch");            sw.Level=(SourceLevels)Enum.Parse(                typeof(SourceLevels), argv[0]);            ts.Switch=sw;            ConsoleTraceListener console=                new ConsoleTraceListener();            ts.Listeners.Add(console);            ts.TraceEvent(TraceEventType.Start, 0,                "Activity trace messages on");            ts.TraceEvent(TraceEventType.Verbose, 0,                "Verbose trace messages on");            ts.TraceEvent(TraceEventType.Information, 0,                "Information trace messages on");            ts.TraceEvent(TraceEventType.Warning, 0,                "Warning trace messages on");            ts.TraceEvent(TraceEventType.Error, 0,                "Error trace messages on");            ts.TraceEvent(TraceEventType.Critical, 0,                "Critical trace messages on");            ts.Flush();            ts.Close();         }     } } 

Trace messages can be sent to a variety of targets, including the console window, a text file, and a XML file. Trace targets are called listeners. A TraceSource type maintains an array of listeners called the Listeners collection. By default, the Listeners collection has a single element, the DefaultTraceListener, which displays trace messages in the Output window of Visual Studio. When the Listeners collection contains more than one listener, trace messages are sent to multiple targets. If the collection contains a ConsoleTraceListener and the XMLWriterTraceListener, trace messages are displayed in the console window and saved into an XML file. Trace listeners receive trace messages sent from a trace source, which is filtered by the trace switch.

Table 12-18 reviews the available predefined listeners.

Table 12-18: Trace Listeners

Listeners

Description

TextWriterTraceListener

This listener forwards trace messages to instances of stream-related classes, such as a TextWriter class.

EventLogTraceListener

This listener forwards trace messages to an event log.

DefaultTraceListener

This listener forwards trace messages to the Output window. This is the default listener and initially the only member included in the Listeners collection.

ConsoleTraceListener

This listener forwards trace messages to a standard output or error stream.

DelimitedListTraceListener

Similar to the TextWriterTraceListener class, this listener forwards messages to an instance of a stream-related class. However, the messages are separated with user-defined delimiters.

XmlWriterTraceListener

This listener saves traces messages as XML-encoded text.

Trace listeners inherit the TraceListener base class, which provides the core functionality of a listener. Most of the methods of the TraceListener mirror those of the TraceSource, Trace, and Debug types. TraceSource, Trace, and Debug forward messages to listeners by invoking identically named trace operations in listeners. All the listeners have a default constructor. Some listeners have multiargument constructors, in which the listener is assigned a name or a destination is defined. For example, the following code creates a new TextWriterTraceListener. It is named samplelistener and writes to the test.txt file.

 TextWriterTraceListener file=new     TextWriterTraceListener("samplelistener", "test.txt"); 

Table 12-19 details the properties of the TraceListener class.

Table 12-19: TraceListener Properties

Property

Type

Attributes

This property gets the custom attributes that are defined in the application configuration file.

StringDictionary

Filter

This property gets and sets the filter of the listener.

TraceFilter

IndentLevel

This property gets and sets the level of indentation.

int

IndentSize

This property gets and sets the amount of indentation per indentation level.

int

IsThreadSafe

This property indicates whether the listener is thread-safe.

bool

Name

This property gets and sets the name of the listener.

string

NeedIndent

This is a protected property, which enables or disables indentation.

bool

TraceOutputOptions

This property gets and sets an enumeration that controls the output options of the listener.

TraceOptions

The values of the TraceOption enumeration are described in Table 12-20. These values are bitwise, which allows multiple options to be combined.

Table 12-20: TraceOptions Values

Value

Description

TraceOptions.CallStack

This option includes the call stack in each trace message. This information is found at the Environment.StackTrace property.

TraceOptions.DateTime

This option includes the date and time in each trace message.

TraceOptions.LogicalOperationStack

This option includes the logical operation stack in each trace message. This information is found at the CorrelationManager.LogicalOperationStack property.

TraceOptions.None

This option excludes all options. The trace message will contain no optional data.

TraceOptions.ProcessId

This option includes the current process identifier in each trace message. This information is found at the Process.Id property.

TraceOptions.ThreadId

This option includes the current thread identifier in each trace message. This information is found at the Thread.MangedThreadId property.

TraceOptions.TimeStamp

This option includes a timestamp in the each trace message. This information is returned from the Stopwatch.GetTimeStamp method.

The listeners of a TraceSource type are managed at the TraceSource.Listeners property. The Listeners property is a TraceListenerCollection type, which implements the IList, ICollection, and IEnumerable interfaces. You can add a listener with the Add method. The TraceListenerCollection type also implements the AddRange method. Call this method to add multiple listeners to the Listeners collection. The AddRange method is overloaded to accept an array of listeners or a TraceListenerCollection type as the sole parameter. The following code adds a ConsoleTraceListener to a Listeners collection:

 ConsoleTraceListener console=     new ConsoleTraceListener(); ts.Listeners.Add(console); 

Trace messages can be filtered at the collection. Listener filters are perfect for identifying important messages in a flood of trace messages. You can focus on a particular problem. Listener filters and trace switch are both filters. The trace switch filters messages from the TraceSource object. Those messages are forwarded to a listener, in which the listener filter further refines the set of trace messages. There are two types of listener filters: SourceFilter and EventTypeFilter. SourceFilter focuses the listener on a specific source. For example, when tracing messages from several classes, you use SourceFilter to limit trace messages to a specific class. EventTypeFilter further refines trace messages based on priority. The priority of EventTypeFilter is usually a subset of the source switch filter.

This is the constructor of the SourceFilter type:

  • SourceFilter constructor syntax:

    • public SourceFilter(string source)

The only parameter is the name of the source. The listener will output only messages from the specific source. If the name is invalid, the filter is ignored.

This is the constructor of the EventTypeFilter type:

  • EventTypeFilter constructor syntax:

    • public EventTypeFilter(SourceLevels level)

The SourceLevels parameter states the priority of allowed trace messages. Other trace messages are ignored.

The following code demonstrates the SourceFilter type. The EventTypeFilter is included in a later example. Three TraceSource instances are defined in the code. A ConsoleTraceListener is also defined, which displays trace messages in the console window. The ConsoleTraceListener .Filter is then updated to display trace messages from only the second source. Later, the SourceFilter is changed to limit trace messages to the first source.

 #define TRACE using System; using System.Diagnostics; namespace Donis.CSharpBook{     public class Starter{         public static void Main() {           TraceSource ts1=new TraceSource("ts1");           TraceSource ts2=new TraceSource("ts2");           TraceSource ts3=new TraceSource("ts3");           SourceSwitch sw=new SourceSwitch("sw",               "Information");           ts1.Switch=sw;           ts2.Switch=sw;           ts3.Switch=sw;           ConsoleTraceListener cs=new ConsoleTraceListener();           ts1.Listeners.Add(cs);           ts2.Listeners.Add(cs);           ts3.Listeners.Add(cs);           // Include only the ts2 source           Console.WriteLine("Filters t1 and t3 messages");           ts1.Listeners[1].Filter=new SourceFilter("ts2");           ts1.TraceInformation("ts1:trace");           ts2.TraceInformation("ts2:trace");           ts3.TraceInformation("ts3:trace");           // Include only the ts1 source           Console.WriteLine("\nFilters t2 and t3 messages");           ts1.Listeners[1].Filter=new SourceFilter("ts1");           ts1.TraceInformation("ts1:trace");           ts2.TraceInformation("ts2:trace");           ts3.TraceInformation("ts3:trace");           ts1.Flush();           ts2.Flush();           ts3.Flush();           ts1.Close();           ts2.Close();           ts3.Close();         }     } } 

Tracing Example

The following example code declares a ZClass and YClass; both classes contain a TraceSource instance. It is a best practice to maintain separate TraceSource instances for individual classes, which affords individualized management of tracing at the class level. For example, you could filter trace messages for specific classes. Expose TraceSource as a static member of the class, which is initialized in the static constructor. Functions of the class leverage the static TraceSource to send trace messages. If using the disposable pattern, clean up the TraceSource object in the Dispose method.

 public class ZClass: IDisposable {     static ZClass() {         ts=new TraceSource("ZTrace");         ts.Switch=new SourceSwitch("sw1", "Information");         ts.Listeners.Add(new ConsoleTraceListener());         TextWriterTraceListener file=new             TextWriterTraceListener("samplelistener", "test.txt");         file.Filter = new EventTypeFilter(SourceLevels.Critical);         file.TraceOutputOptions=TraceOptions.DateTime;         ts.Listeners.Add(file);     }     static private TraceSource ts;     // partial listing     public void Dispose() {         ts.Flush();         ts.Close();     } } 

The following code is a complete listing of the sample code used in this section. Main also has a trace source, which sends trace messages to listeners for the Output and Console windows. Both the ZClass and YClass have separate trace sources. The methods of the ZClass and YClass use their respective TraceSource instances to send trace messages. To demonstrate both techniques, the TraceEvent and TraceData methods are called. The hash codes of the objects are used as the trace identifier, which identifies trace messages by class type and instance. This is sometimes useful. The trace source of the ZClass sends trace messages to listeners for the Output window, Console window, and a text file. The switch of that trace source forwards all trace messages. However, the filter for the Console listener restricts output to error type messages. The YClass trace source sends trace messages of error priority to listeners for the Output and Console windows. The switch limits the trace messages to informational messages.

 #define TRACE using System; using System.Diagnostics; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             TraceSource ts=                 new TraceSource("StarterTrace");             ts.Switch=new SourceSwitch("sw3");             ts.Switch.Level=SourceLevels.ActivityTracing;             ts.Listeners.Add(new ConsoleTraceListener());             ts.TraceEvent(TraceEventType.Start,                 0, "Starting");             ZClass obj1=new ZClass();             obj1.MethodA();             obj1.MethodB(1,2,3);             YClass obj2=new YClass();             obj2.MethodC();             ZClass obj3=new ZClass();             obj1.MethodA();             obj1.MethodB(4,5,6);             ts.TraceEvent(TraceEventType.Stop,                 0, "Stopping");             obj1.Dispose();             obj2.Dispose();         }     }     public class ZClass: IDisposable {         static ZClass() {             ts=new TraceSource("ZTrace");             ts.Switch=new SourceSwitch("sw1", "All");             ts.Listeners.Add(new ConsoleTraceListener());             TextWriterTraceListener file=new                 TextWriterTraceListener("samplelistener", "test.txt");             file.Filter = new EventTypeFilter(SourceLevels.Critical);             file.TraceOutputOptions=TraceOptions.DateTime;             ts.Listeners.Add(file);         }         static private TraceSource ts;         public void MethodA() {             ts.TraceEvent(TraceEventType.Error,                 GetHashCode(), "ZClass.MethodA");         }         public void MethodB(int parama, int paramb,                 int paramc) {             ts.TraceData(TraceEventType.Critical,                 GetHashCode(), "ZClass.MethodB", parama,                 paramb, paramc);         }         public void Dispose() {             ts.Flush();             ts.Close();         }     }     public class YClass: IDisposable {         static YClass() {             ts=new TraceSource("YTrace");             ts.Switch=new SourceSwitch("sw2", "Information");             ts.Listeners.Add(new ConsoleTraceListener());             ts.Listeners[1].IndentSize=4;             ts.Listeners[1].IndentLevel=2;         }         static private TraceSource ts;         public void MethodC() {             ts.TraceEvent(TraceEventType.Error,                 GetHashCode(), "YClass.MethodC");         }         public void Dispose() {             ts.Flush();             ts.Close();         }     } } 

Configuration File

Trace switches, listeners, and listener filters are configurable in an application configuration file. Actually, this is the best practice and is preferable to programmatic configuration. Developers can update the specifics of tracing without recompiling the application. The application configuration file has the same name as the target assembly plus a .config extension. For example, the application configuration file for hello.exe is hello.exe.config. The application configuration file should be in the same directory as the assembly. For a software system, you can configure multiple applications with a publisher policy file, which is deployed in the global assembly cache. Visit this link for a "how to" on publisher policy files: msdn2.microsoft.com/en-us/library/dz32563a. You can configure tracing in both the configuration file and programmatically. Where there is overlap, programmatic configuration takes precedent.

In the configuration file, the tracing is placed within the system.diagnostics element. For the co mplete explanation of the system.diagnostics element, open this link: msdn2.microsoft.com/en-us/library/1txedc80.

 <?xml version="1.0" encoding="utf-8" ?> <configuration>     <system.diagnostics>     </system.diagnostics> </configuration> 

Sources element Define the trace sources within the sources element. A specific trace source is declared in a source element. The important attributes of the source element are name and switchName. The name attribute is the name of the trace source, and switchName names the switch assigned to the trace source. Here is an example:

 <sources>     <source name="ZTrace" switchName="sw1">     </source> </sources> 

Listeners element The listeners of a particular source are listed within the source elements. The listeners element encapsulates the listeners of a trace source. Individual listeners are added to the Listeners collection with the add element. The key attributes of the add element are type, name, traceOutputOptions, and initializeData. The type attribute is the kind of listener. The name attribute is the name assigned to the listener. The initializeData attribute is additional data used to create the listener, such as the target file name. The traceOutputOtions attribute adds optional data to trace messages of the listener, such as a timestamp. Here are example elements:

 <sources>     <source name="ZTrace" switchName="sw1">         <listeners>             <add initializeData="data.txt"                 type="System.Diagnostics.TextWriterTraceListener"                 name="tListener" />             <add name="cListener" />         </listeners>     </source> </sources> 

sharedListeners element The listeners element assigns a listener to a specific trace source. You can also share listeners. Listeners are shared between trace sources in the sharedListeners element. A shared listener is added with an add element. Shared listeners are added to a specific trace source as a regular listener. However, the add element need only have the name attribute, which identifies the shared listener:

 <system.diagnostics>     <sharedListeners>         <add type="System.Diagnostics.ConsoleTraceListener"             name="cListener"             traceOutputOptions="None" />     </sharedListeners> </switches> 

Switches element Switches are defined within the switches elements. An individual switch is added with an add element. The basic attributes are name and value . The name attribute is the name of the switch, and the value attribute is the filter for the trace message. Here is an example:

     <system.diagnostics>         <switches>             <add name="sw1" value="Critical" />             <add name="sw2" value="Information" />         </switches>     </system.diagnostics> </configuration> 

Tracing Example with a Configuration File

The programmatic example of tracing has been rewritten to leverage an application file. The following code is the new source code for the application. The switch and collection code is removed from the source code and placed in the configuration file.

 #define TRACE using System; using System.Diagnostics; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             TraceSource ts=                 new TraceSource("StarterTrace");             ts.TraceEvent(TraceEventType.Start,                 0, "Starting");             ZClass obj1=new ZClass();             obj1.MethodA();             obj1.MethodB(1,2,3);             YClass obj2=new YClass();             obj2.MethodC();             ZClass obj3=new ZClass();             obj1.MethodA();             obj1.MethodB(4,5,6);             ts.TraceEvent(TraceEventType.Stop,                 0, "Stopping");             obj1.Dispose();             obj2.Dispose();         }     }     public class ZClass: IDisposable {         static ZClass() {             ts=new TraceSource("ZTrace");         }         static private TraceSource ts;         public void MethodA() {             ts.TraceEvent(TraceEventType.Error,                 GetHashCode(), "ZClass.MethodA");         }         public void MethodB(int parama, int paramb,                 int paramc) {             ts.TraceData(TraceEventType.Critical,                 GetHashCode(), "ZClass.MethodB", parama,                 paramb, paramc);         }         public void Dispose() {             ts.Flush();             ts.Close();         }     }     public class YClass: IDisposable {         static YClass() {             ts=new TraceSource("YTrace");         }         static private TraceSource ts;         public void MethodC() {             ts.TraceEvent(TraceEventType.Error,                 GetHashCode(), "YClass.MethodC");         }         public void Dispose() {             ts.Flush();             ts.Close();         }     } } 

The following application configuration file sets up tracing for the application. There are some differences from the first example. For example, the three trace sources share the same Console listener. The YTrace trace source also has an XML listener. Otherwise, the results are virtually the same as the earlier code.

 <?xml version="1.0" encoding="utf-8" ?> <configuration>     <system.diagnostics>         <sources>             <source name="StarterTrace" switchName="sw3">                 <listeners>                     <add name="cListener" />                 </listeners>             </source>             <source name="ZTrace" switchName="sw1">                 <listeners>                     <add initializeData="data.txt"                         type="System.Diagnostics.TextWriterTraceListener"                         name="tListener" />                     <add name="cListener" />                 </listeners>             </source>             <source name="YTrace" switchName="sw2">                 <listeners>                    <add name="cListener" />                    <add name="xListener" />                 </listeners>             </source>         </sources>         <sharedListeners>             <add initializeData="data.xml" type="System.Diagnostics.XmlWriterTraceListener"                 name="xListener" traceOutputOptions="DateTime" />             <add type="System.Diagnostics.ConsoleTraceListener" name="cListener"                 traceOutputOptions="DateTime" />         </sharedListeners>         <switches>             <add name="sw1" value="Information" />             <add name="sw2" value="All" />             <add name="sw3" value="ActivityTracing" />         </switches>     </system.diagnostics> </configuration> 

DebuggerDisplayAttribute

The DebuggerDisplayAttribute type controls how values are displayed in a debugger window. This attribute is valid for assembly, class, struct, enum, indexer, property, field, and delegate constructs. You cannot use DebuggerDisplayAttribute on methods. When this attribute is used as an assembly-level attribute, the Target property must be assigned the name of the applicable type. The DebuggerDisplayAttribute type is also found in the System.Diagnostics namespace.

The DebuggerDisplayAttribute type has a one-argument constructor. The single argument, which is a string, is the display value of the type in the debug window. The value can contain an expression. Expressions must be enclosed in curly braces: {expression}. Constants, static members, and instance members are valid in the expression. Prefix static members with the class name. The expression cannot contain pointers, aliases, or local variables.

The DebuggerDisplayAttribute type is inheritable. A derived class inherits the attribute from the base class. However, the derived class can redefined DebuggerDisplayAttribute as desired.

Figure 12-45 shows the values in a debug window for the ZClass and YClass instances without DebuggerDisplayAttribute.

image from book
Figure 12-45: View of the ZClass and YClass instances before applying DebuggerDisplayAttribute

The following code decorates the ZClass and YClass with the DebuggerDisplayAttribute type. The ZClass is assigned the value NewName, which is overridden in the derived class. In addition, ZClass.fielda is adorned with the attribute, which contains an expression in this circumstance.

 [DebuggerDisplay("NewName")] class ZClass {     public static int test = 1;     public virtual void MethodA()     {         int vara=5, varb=10;         Console.WriteLine("{0} {1}", vara,             varb);     }     public void MethodB()     {         Console.WriteLine("ZClass.MethodB");         Console.WriteLine("ZClass.fielda {0}",             fielda);     }        [DebuggerDisplay("fielda = {fielda}")]     private int fielda = 5; } [DebuggerDisplay("DerivedName")] class YClass : ZClass {     public override void MethodA()     {         Console.WriteLine("YClass.MethodA");         Console.WriteLine("Fieldb: {0}", fieldb);     }         private int fieldb = 10; } 

Figure 12-46 shows the results of using the DebuggerDisplayAttribute type with the ZClass and YClass .

image from book
Figure 12-46: View of ZClass and YClass types in the debugger window after applying the DebuggerDisplayAttribute type

DebuggerBrowsableAttribute

Another debugger attribute is the DebuggerBrowsableAttribute type, which determines how a member is displayed in the debugger window. This attribute is valid for properties, indexers, and fields.

DebuggerBrowsableAttribute has one constructor, which is a one-argument constructor. The parameter is the DebuggableBrowsableState enumeration. Table 12-21 lists the elements of the enumeration.

Table 12-21: DebuggableBrowserState Values

Value

Description

Never

This element hides the member in the debugger window.

Collapsed

This element displays the member, which is collapsed initially. This is the default.

RootHidden

If the element is an array or collection, this element hides the root member but displays the child elements. For example, when applied to a property that is an integer array, the integer elements are displayed instead of the array itself.

Figure 12-47 shows the values for the ZClass and YClass types without the DebuggerBrowsableAttribute type.

image from book
Figure 12-47: View of ZClass and YClass types before applying the DebuggerBrowsableAttribute type

The following code shows the ZClass and YClass types decorated with DebuggerBrowsableAttribute. In the ZClass, the array related to the propInts property is displayed. In the YClass type, fieldb is hidden.

 class ZClass {     public static int test = 1;     public virtual void MethodA()     {         int vara=5, varb=10;         Console.WriteLine("{0} {1}", vara,             varb);     }     public void MethodB()     {         Console.WriteLine("ZClass.MethodB");         Console.WriteLine("ZClass.fielda {0}",             fielda);     }     private int[] Ints ={ 1, 2, 3, 4, 5 };     [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]     public int[] propInts     {         get         {             return Ints;         }     }     private int fielda = 5; } class YClass : ZClass {     public override void MethodA()     {         Console.WriteLine("YClass.MethodA");         Console.WriteLine("Fieldb: {0}", fieldb);     }     [DebuggerBrowsable(DebuggerBrowsableState.Never)]     private int fieldb = 10; } 

Figure 12-48 shows the results of using the DebuggerBrowsableAttribute type on the ZClass and YClass.

image from book
Figure 12-48: View of ZClass and YClass types after applying DebuggerBrowsableAttribute

DebuggerTypeProxyAttribute

The DebuggerTypeProxyAttribute type names a type as a proxy for another type. The type proxy is displayed instead of the type in debugger windows. The attribute type is DebuggerTypeProxyAttribute. This attribute is valid for assembly, class, and struct constructs. When used at the assembly level, the target name property must be assigned the name of the target type.

The proxy type must have a one-argument constructor that accepts an instance of the target type. For this reason, it is recommended that the proxy type be nested within the target type. This provides the proxy type constructor easy access to the instance of the surrounding object. Only public members of the proxy are visible in the debugger window.

The DebuggerTypeProxyAttribute type is useful for hiding sensitive data within a type. In the following code, XClass has a password field. This field should not be exposed during live debugging because passwords represent sensitive data. The DebuggerTypeProxyAttribute type in the sample code names XClassDebug as the proxy. XClassDebug hides the password field and displays an appropriate value, which is "Not Available".

This is the code for the XClass and the nested XClassDebug class, which is the proxy class:

 [DebuggerTypeProxy(typeof(XClassDebug))] public class XClass {     public XClass(string _password)     {         password=_password;     }     private string password;     internal class XClassDebug     {         public XClassDebug(XClass obj)         {         }         public string password = "Not Available";     } } 

Dump Files

Visual Studio 2005 can open and interpret dump files from managed, native, or mixed-mode applications. A dump is a snapshot of an application's memory that lets a developer debug an application at a convenient location and time, which is particularly beneficial for debugging production applications. Dumps created on a production machine can then be debugged on another machine, without interfering with the production machine. Several tools are available for creating a dump, including Windbg, Dr. Watson, Autodump+ (ADPlus), and Visual Studio. Visual Studio can create dumps while debugging native applications, such as Microsoft Visual C++ applications. This feature is not available in managed projects. Dump files and other advanced debugging topics are discussed in the next chapter, "Advanced Debugging."

In Visual Studio, dump files are opened as projects. Choose the Open Solution/Project menu command from the Open submenu of the File menu. Dump files have the .dmp extension. After opening the file, start a debug session to examine the dump file. The Debug Start menu command (F5) on the Debug menu starts a debug session. When debugging a dump, a breakpoint is hit almost immediately. You can then debug and diagnose the dump using the various debug windows. Figures 12-49 and 12-50 show the Call Stack and Modules debug windows, which provide different views of the dump.

image from book
Figure 12-49: Call Stack window for a dump

image from book
Figure 12-50: Modules window for a dump

The Debug windows of Visual Studio can provide a native perspective of an unmanaged application. This is helpful with dumps of managed applications, but a managed view is also sometimes merited. Load the Son of Strike debugger extension (SOS.DLL) for a managed perspective of a dump. Load Son of Strike with the .load sos command in the Immediate window. You can then issue various Son of Strike commands, which are prefixed with an exclamation point. In Figure 12-51, Son of Strike is loaded. Afterward, the DumpHeap command is invoked. Output is shown in the Immediate window. Some Son of Strike commands are thread-specific. Set the thread context in the Thread window before issuing one of those commands.

image from book
Figure 12-51: Son of Strike in the Immediate window




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net