The Watch Window


If I had to give an Academy Award for technical achievement and overall usefulness in Visual Studio, the Watch window would win hands down. One idea that companies creating development tools for other environments and operating systems haven't figured out at all is that if developers can easily develop and debug their applications, they'll more likely flock to that environment or platform. In previous versions of Visual Studio, the power offered by the Watch window and its related cousins, the QuickWatch dialog box, Autos window, Locals window, This window, and Me window, has been legendary. With Visual Studio 2005, the new power of DataTips, Visualizers, and the Immediate window is something that will have developers debugging faster than ever before.

I want to make sure to point out that you can use any of the related Watch windows to change a variable's value. Unfortunately, many developers coming over from other environments, and a few who have been doing Windows development for many years, aren't aware of the capabilities. Let's take the Autos window as an example. You just select the variable or the child variable you want to change, and click once on the value field for that variable. Simply type in the new value and you've changed the variable.

Many developers treat the Watch window as a read-only place in which they drop their variables and watch. What makes the Watch window exciting is that it has a complete expression evaluator built in. If you want to see something as an integer that's not an integer, simply cast or convert it in the same way you would if you were programming in the currently active programming language. Here's a simple example: Suppose CurrVal is declared as an integer and you want to see what it evaluates to as a Boolean. In the Name column of the Watch window, in C# enter (bool)CurrVal, or for Visual Basic, enter CBool(CurrVal). The value is displayed as true or false as appropriate.

Changing an integer to a Boolean might not be that exciting, but the ability of the Watch window to evaluate expressions gives you the ultimate code-testing trick. As I discussed in the "What's the secret to debugging?" section of Chapter 4, code coverage is one of the goals you need to strive for when doing your unit testing. If, for example, you have a conditional expression inside a function, and it's difficult to see at a glance what it evaluates to so that you can step into the true or false branch as appropriate, the Watch window becomes your savior. Because there's a full expression evaluator built in, you can simply drag the expression in question down to the Watch window and see what it evaluates to. Granted, there are some restrictions. If your expression calls all sorts of functions instead of using variables, you could be in trouble. If you look at my code, I use only local variables in conditions that I can see the result of the expression in the Watch window. Some of you might be truth table geniuses and be able to see how those expressions evaluate off the top of your heads, but I certainly am not one.

To make this clearer, let's use the next expression as an example. You can highlight everything inside the outer parentheses and drag it to the Watch window. However, because it's on three lines, the Watch window interprets it as three separate lines in its display. I still do that so that I can copy and paste the two lines into the first one and thus build my final expression without too much typing. Once the expression is entered on one line in the Watch window, the value column is either true or false, depending on the values in the variables.

if ( ( RunningState.eRunning == currentState    ) ||      ( RunningState.eException == currentState  ) &&      ( true == seenInitialCall )  )


The next step is to put each of the variables that make up a part of the expression in their own entries in the Watch window. The really cool part is that you can start changing the values of the individual variables and see that the full expression on the first line automatically changes based on the changing subexpressions. I absolutely love this feature because it helps you see the data coverage you need to generate in your unit tests.

Format Specifiers and Property Evaluation

If you're coming from a C++ background, you'll remember the, code values, called format specifiers, that you could use to influence exactly how you wanted the data to be displayed. If you're a C# developer, some of those format specifiers have reappeared to make your life easier. For example, if you have a variable i and you type i,h in the Watch window, the number will be displayed as hexadecimal instead of the decimal default. By the way, you can force the default Watch window display to hexadecimal by right-clicking in the Watch window and selecting Hexadecimal Display from the shortcut menu. If you have set the default display to Hexadecimal, you can specify the ,d format specifier to display the data as decimal. The ,raw formatting code is used when DebuggerTypeProxyAttributes have been applied to a class, which I will explain more in detail in the "Expanding Your Own Types" section later in the chapter.

The ,nq formatting code is especially handy because to make the string more readable, it will display a string without the escaped quotes. For example, if you have the XML element, the default view of the inner text would show the string like the following: "<book genre=\ "novel\" ISBN=\ "1-861001-57-5\" misc=\ "sale item\"/>." After you add the ,nq on the end of the string property, the display changes to "<book genre="novel" ISBN="1-861001-57-5" misc="sale item"/>" that is much easier to read. Personally, I wish the Watch window would default to the non-quoted string display.

If you have Just My Code enabled, and you want to see the private members of an object, you'd specify the ,hidden code after the variable name. Note that the Visual Studio documentation is incorrect and calls this formatting code ,private.

The last format specifier I want to mention is ,ac, which exists to force expression evaluation. The documentation lists this format specifier with a capital A, which is incorrect. In order to talk about the ,ac specifier, I have to talk about implicit property evaluation. Whereas the format specifiers are annoyingly C# only, implicit property evaluation applies to all .NET languages, and it's important that you understand the ramifications.

For .NET developers, it's second nature to see the values of properties on objects in any of the Watch window family of windows. What the debugger's doing is actually calling the property's get method in the context of debuggee. In most cases, there's no trouble at all having the debugger help you out that way. Occasionally, a property evaluation inside the debugger can cause a side effect that causes a great deal of trouble. If there is a property getter that makes a database query that changes data, the property evaluation in the debugger will certainly be changing the state of your application. I know no one reading this would ever have a property getter changing data, but you can't always force your coworkers to follow best practices.

If there's any hint that the debugger property evaluation will cause you problems, you'll want to turn it off by going to the Options dialog box, clicking the Debugging node, General property page, and clearing the Enable Property Evaluation and Other Implicit Function Calls check box. You can toggle this option all you want during a debugging session to control the debugger behavior. Although I would prefer a menu command or a toolbar button to control the option, it's not too onerous to toggle it. You'll know that implicit property evaluation is turned off when you start debugging because you'll see "Property evaluation is disabled in debugger windows. Check your settings in Tools.Options.Debugging.General." for Visual Basic and "Implicit function evaluation is turned off by user." for C#. Interestingly, turning off property evaluation does not disable calling properties and methods on conditional breakpoint modifiers.

In previous versions of the debugger, once you turned off implicit property evaluation, there was no way to see those property values without turning it fully back on. Fortunately, the Visual Studio 2005 debugger is much more forgiving, and you have various ways to evaluate specific properties. For all languages, the easiest thing to do is to click the green refresh button on the right side of the Value column. Figure 5-7 shows the Watch window with the refresh buttons called out. As you can see, you can click the refresh button for the entire object so all properties in the class are shown, or you can do just the one property you're interested in seeing.

Figure 5-7. Controlling debugger-implicit property evaluation


If you're debugging a C# application, you can add the ,ac format specifier to that property or the class as a whole. This will tell the debugger that you always want to evaluate the specific property or all properties on the object every time you stop the debugger.

One interesting item I noticed with implicit property evaluation turned off is that when you add an object to the Watch window, the Watch window will evaluate all the properties on that object so you can see their values. It's intuitively what you would expect, but if you drag and drop that one object with a nasty-side-effect property, you've just caused it to execute. Just keep that in mind when you are adding scary objects to the Watch window. What you'll want to do instead is stop in another scope where the variable is not defined and add the value you want to watch there. That way it's in the Watch window when you stop, so the debugger won't evaluate the properties when it stops in the real location.

Before I jump to my favorite new feature in the Watch window, I wanted to mention two pseudo variables you can display in the Watch window. If you've turned off the Exception Assistant, the window that shows the unhandled exception, the C#-only $exception pseudo variable will be automatically added to the Locals window so you can see the unhandled exception. The new pseudo value, $user, will display the account information for the current thread and indicate if it's impersonating.

Make Object ID

Very few C# developers (yes, this is a C#-only feature) have bothered to right-click in the Watch window family. If you have in the past, you might think that nothing changed because the shortcut menu still has the options for toggling the display between hexadecimal and decimal display in addition to the usual copy and paste. Depending on where you click, one of the most important but least mentioned of the new debugger tricks shows up in the middle of the menu: Make Object ID. You'll see this only when right-clicking on a value that's live, which means it's either in a local variable, parameter, or an object field, in the current scope.

After you've selected Make Object ID, it doesn't look like much happened, but if you look closely at the Value field, you might see something interesting. Figure 5-8 shows the results of selecting Make Object ID in an Autos window.

Figure 5-8. Result of selecting Make Object ID from the shortcut menu


Although you still see the object type in curly braces, you now also see a {1#}. That signifies the object ID assigned to that object instance. That might not sound that exciting, but if you go over to the Watch window and enter 1#, as I've done in Figure 5-9, you'll start to get an inkling of the power you just unleashed.

Figure 5-9. Using an object ID in the Watch window


The object ID is where a managed debugger can keep an eye on an object anywhere in the garbage-collected heap regardless of scope. Therefore, if you want to watch a local variable from a method twenty items up the stack, create an object ID for that object, and you'll see it live no matter where you are. You can even have object IDs for values that are in other threads. Even better, you'll see this object no matter what generation it bounces to, and if it's collected, the display for that object ID will be unavailable.

I don't know how many times I've added a temporary static field to a class so I can keep track of a particular value or set of values. Now with the amazingly cool Make Object ID value, you can keep track of those objects using the debugger and not have to change your code. The only drawbacks are that you have no control over the naming so if you create twenty or thirty object IDs, you'll have to write down what each number means in order to remember them. Alternatively, you could develop a photographic memory to avoid keeping track of them manually.

As I mentioned at the beginning of this section, this is a C#-only feature, although it also works in Microsoft Visual J#. I cannot understand why this amazingly useful feature isn't present in Visual Basic applications. Sometimes I have to wonder if the Visual C# and Visual Basic teams work at the same company. It's as if the Visual Basic team is from Venus and the Visual C# team is from Mars. My hope is that when the next version of Visual Studio ships, the teams will learn how to communicate so we have the same features no matter what .NET language we're using.

DataTips

One of the sexiest items of any Visual Studio 2005 demo is the new DataTips in the debugger. As we've all seen, they've grown way past being simple tool tips into a complete Watch window jammed into an automatic pop-up message box. I had to debug a .NET 1.1 problem with Visual Studio .NET 2003 the other day and realized just how amazing the new DataTips are when you don't have them.

Although most of us have played with the DataTips, I want to mention a few cool tricks that you might not be aware of. The first is that any time a DataTip appears, you can press the Ctrl key to make it go to nearly transparent. Because DataTips can expand to cover the entire screen, this little trick works great to see the code you're working on while still enabling you to keep the expanded DateTip available.

Although the only way to get a DataTip up is to move the mouse over it, once a DataTip is active, it does support a number of keyboard options. Page Up and Page Down scroll through the available items as does the mouse wheel. The up and down arrows move through the list, and if you are on an item that's expandable, the right arrow key will expand the child items. Once you are finished with the expanded display, the left arrow will collapse the opened values.

Because the DataTips are the complete Watch window in a tool tip, you can also edit values directly in the DataTip itself. You can either click in the value area to the right of the value or press the F2 key if you've highlighted the item with the Up and Down arrow keys. Obviously, the DataTip won't let you edit read-only values. If you have doubts on what you can edit, right-click the item in the DataTip, and if the Edit Value command is unavailable, it's read-only. If the Edit Value item is available, it's the third way to enable editing in a DataTip.

The shortcut menu on a DataTip has nearly the same items as present in a normal Watch window, so you can do the magical Make Object ID (C# only) any time you need it. In addition to being able to add a value to the Watch window from the DataTip, it also has the Copy Expression menu item, which copies only the variable/ class.field name and Copy Value, which copies only the value of the highlighted item.

Expanding Your Own Types

Although the Watch window family does an excellent job of displaying data, it doesn't always display the data in the most concise form. This isn't the fault of the debugger; it's just that there's no way the debugger knows what the key data for every class is. Every class has those two or three items that at a glance tell you everything you need to know so you don't have to expand the class and wade through thirty fields to find those items.

Fortunately, the improved debugger in Visual Studio 2005 offers a much better way to show key data than before. Past versions relied on a file called MCEE_CS.dat hidden down deep in the Visual Studio installation directorythat file worked only for C#. By keeping these special expansions separate from the classes themselves, unless you were hyper-vigilant with version control, other developers couldn't take advantage of your helpful expansions.

C# developers will get the first of these new expansions simply by overriding ToString, which in many cases shows nearly all the key data you need at a glance. Now instead of seeing the type in the Value field of the Watch window family or the DataTip, you'll see the result of the debugger calling ToString on the instance. If you've wondered how the debugger could always show you the current string in a StringBuilder class, this is exactly what's happening.

If having the ToString called on your objects is causing side effects, the evaluation will be turned off when in the Options dialog box, Debugger node, General page, you clear Call ToString() on objects in variables windows (C# only). Because that option is under Enable property evaluation and other implicit function calls, disabling the parent option will disable all ToString calls also.

To automatically have the debugger display the key data about your classes, you'll apply the new DebuggerDisplay-Attribute to the class where you'll specify the data to display in the Value column when viewing the data in the Watch window family. For example, the ComplexNumber class in Listing 5-3 shows applying the DebuggerDisplay-Attribute to a class. As you can see in the listing, the expression "{_Real}.{_Imaginary}i" will evaluate the _Real and _Imaginary fields whenever an instance of the class is evaluated by the debugger because any value in curly braces is assumed to be a field in the instance. Any value outside of curly braces is assumed to be hard-coded text, which is why an i will always follow the evaluations of the two fields in this example. Being able to see 3.4i at a glance is so much faster than clicking and expanding the class to look at the individual field values.

Listing 5-3. DebuggerDisplayAttribute Example

[View full width]

<DebuggerDisplay("{_Real}.{_Imaginary}i")> _ Public Class ComplexNumber ' Setting the state to Never tells the debugger to hide ' this field in any display. Since there's a Real ' property, hiding this internal field makes the display ' easier to deal with. <DebuggerBrowsable(DebuggerBrowsableState. Never)> _ Private _Real As Integer ' The debugger will show this value with the Imaginary property. <DebuggerBrowsable(DebuggerBrowsableState .Never)> _ Private _Imaginary As Integer Public Sub New(ByVal Real As Integer, ByVal Imaginary As Integer) _Real = Real _Imaginary = Imaginary End Sub Public Sub New() _Real = 0 _Imaginary = 0 End Sub Public Property Real() As Integer Get Return (_Real) End Get Set(ByVal value As Integer) _Real = value End Set End Property Public Property Imaginary() As Integer Get Return (_Imaginary) End Get Set(ByVal value As Integer) _Imaginary = value End Set End Property Public Shared Operator +(ByVal right As ComplexNumber, _ ByVal left As ComplexNumber) As ComplexNumber Dim ret As ComplexNumber = New ComplexNumber(right.Real + left.Real, _ right.Imaginary + left.Imaginary) Return (ret) End Operator Public Overrides Function ToString() As String Return (String.Format("{0}.{1}i", _ Real.ToString(), _ Imaginary.ToString())) End Function End Class



As soon as I saw that the DebuggerDisplayAttribute can process fields, I had to try a test in which I tried a method call in the passed-in expression to see what the debugger is able to handle. Using a C# version of the ComplexNumber class, I tried setting the DebuggerDisplayAttribute expression to "ToString()" because that method was doing the same thing as the expression evaluating the string directly. That worked great in the C# driver program. However, when I tried the same expression in the Visual Basic version of ComplexNumber, I didn't see the string I expected. Only the type appeared in the Value column of the Watch window, as though I didn't have any DebuggerDisplayAttribute at all. Interestingly, using the properties in the expression for the Visual Basic example, "{Real}.{Imaginary}i" (notice the lack of underscores) shows that the properties are being executed by the debugger. With a little more exploration, it turns out that Visual Basic expression evaluator used with the DebuggerDisplayAttribute will not call any methods with parenthesis.

Because of the limitations in the Visual Basic evaluation, to ensure that the appropriate expression appears, you'll want to limit yourself to only fields and properties in your DebuggerDi splayAttribute values no matter what language you program in. Even given that minor limitation, I'm thrilled with DebuggerDisplayAttribute because the super-quick debugger display now stays with the class, and the debugger automatically uses it. Even more exciting is that if you have a class that is derived from a class that has a DebuggerDisplayAttribute defined, the derived class will automatically get the benefit because the debugger will scoot down the class derivation chain and use the DebuggerDisplayAttribute that was found.

If you're looking for a good project, you might want to consider writing a Code Analysis rule that will look to see if a public class does not have a DebuggerDisplayAttribute defined. If not, you could report an error. I will give you extra credit if you have the rule look up the class derivation chain and not report the error if a base class has a DebuggerDisplayAttribute defined. For more information on writing Code Analysis rules, see Chapter 8, "Writing Code Analysis Rules."

Listing 5-3 also shows the DebuggerBrowsableAttribute, which allows you to control how the Watch window expands and displays a type. In the example, I've applied the DebuggerBrowsableAttribute with the DebuggerBrowsableState set to Never to the two fields in the class. Because properties expose those two field values, the actual fields will not be displayed if you expand a ComplexNumber type. By condensing the data display, without hiding any actual data, you'll see the important data more quickly, which is especially important in DataTips.

Back in the "Format Specifiers and Property Evaluation" section of this chapter, I discussed how the implicit property evaluation could cause side effects because the debugger is executing the property. Although you should avoid property getters that have side effects, in some cases you can't. If you're faced with that conundrum, you could apply the DebuggerBrowsableAttribute with the DebuggerBrowsableState set to Never, and the debugger will never execute the property. Now you won't have to worry about the debugger executing that particular property. Of course, any time you apply a DebuggerBrowsableAttribute, you need to have a comment as to why you're applying it so maintenance developers and coworkers know why you're hiding an item from the debugger.

The DebuggerBrowsableState.Never state will usually be what you pass to DebuggerBrowsableAttribute. However, if you have a collection or array and want to hide the variable name but expand the data, you can specify DebuggerBrowsableState.RootHidden. This enumeration is used in conjunction with the DebuggerTypeProxyAttribute, which I'll discuss in a moment. The last of the DebuggerBrowsableState trio is Collapsed, which you'll never use because that's the default state for any type displayed in the Watch window family. The last thing I want to mention about the DebuggerBrowsableAttribute is that numerous places in the Visual Studio documentation say that Visual Basic debugging does not support the DebuggerBrowsableAttribute, which is incorrect.

If you want even more control over how the debugger displays your type, you can tell the debugger that you have a class that should be used by the debugger to show its fields and properties instead of your real type with the DebuggerTypeProxyAttribute. This jumps into the realm of the super-advanced, but if you need it, you'll thank the debugger team for adding the capability. To see how DebuggerTypeProxyAttribute works, see the code in Listing 5-4, which is an excellent example that Habib Heydarian, the Program Manager for the Visual Studio debugger, showed me for using DebuggerTypeProxyAttribute.

The constructor for DebuggerTypeProxyAttribute takes either a type or a string or both, which is the class the debugger will instantiate to do the display. Traditionally, the class used to display is an internal or Friend class nested inside the main class in order to allow easy access to private members. However, there is nothing stopping you from having the display class be anywhere in the module. The only requirement of the display class passed to DebuggerTypeProxyAttribute is that the constructor on the class must have the type to display as a parameter. Inside the display class, you can have as many properties or fields as necessary to be shown by the debugger.

Before you look at Listing 5-4, which works with SecureString types, it's worth showing you what the default display looks like in the debugger. Figure 5-10 shows a SecureString, and as you can see, there's no way you can find the real string at all.

Figure 5-10. Default display for SecureString


The code in Listing 5-4 is not a full program, and I will discuss a bit more about where it's loaded and how it's executed in the "Adding DebuggerDisplayAttribute without Source Code" section later in this chapter.

Listing 5-4. DebuggerTypeProxyAttribute example

[View full width]

[assembly: DebuggerTypeProxy ( typeof ( Example .SecureStringDebuggerProxy ) , Target = typeof ( SecureString ) )] namespace Example { public class SecureStringDebuggerProxy { private string m_value; public SecureStringDebuggerProxy ( System.Security.SecureString s ) { IntPtr ptr = Marshal .SecureStringToBSTR ( s ); this.m_value = Marshal .PtrToStringBSTR ( ptr ); Marshal.FreeBSTR ( ptr ); } public string Value { get { return m_value; } } } }



When the code in Listing 5-4 is used, the display looks like that in Figure 5-11. As you can see, there are a lot fewer items, and the Value field shows the string, which is the exact same one shown in Figure 5-10. Even a manager can figure out that Figure 5-11 is much easier to understand.

Figure 5-11. DebuggerTypeProxyAttribute display for SecureString


The Raw View element is a C#-only feature that allows you explicit access to the actual instance data. This is very helpful if you suspect that the DebuggerTypeProxyAttribute has a bug or may be hiding data from you. If you want to force off the DebuggerTypeProxyAttribute for a class, you can add the ,raw formatting specifier after the variable name in the Watch window.

Adding DebuggerDisplayAttribute Without Source Code

I was working on a bug in a Visual Basic application that used a ton of StringBuilder classes, and I was lamenting the automatic C# ToString expansion so I could see the strings automatically as they were built up. After I was done with the debugging job, I set out to see if it were possible to provide my own expansions for classes where I didn't have the source code. The great news is that I found the undocumented way of accomplishing the goal, and it turned out to be extremely easy.

As with any reverse engineering in .NET, it always starts with Reflector from Lutz Roeder. I'd noticed that items like WebControls.Button and any Exception derived type showed custom text in the Value column so they must have had a DebuggerDisplayAttribute on them. Checking with Reflector showed that they didn't have any DebuggerDisplayAttribute. In fact, almost no classes in the Framework Class Library (FCL) have DebuggerDisplayAttribute on them. About the only ones that do are those in the System.Collections.Generic namespace.

I looked at the debugger binaries but didn't see that they had any hard-coded values in them that would show custom values for known types. Maundering through the debugger binaries, I thought I'd look in the directories where debugger visualizers, which I'll talk about later in the chapter, were loaded. In the C:\Documents and Settings\ user_name\My Documents\Visual Studio 2005\Visualizers directory are two files, AutoExp.cs and AutoExp.dll. Being that the old C++ expansion file was called AutoExp.dat, I figured I was on to something, and a quick look in AutoExp.cs showed that that is the file that contains the code for the DebuggerDisplayAttributes and is compiled into AutoExp.dll, which is what the debugger loads to show the custom values.

The trick to specify a DebuggerDisplayAttribute where you don't have source code is to specify it as an assembly-level attribute and use the Target parameter to specify the type. For example, the default AutoExp.cs (and compiled DLL) shows the following custom expansion for a Point structure:

[assembly: DebuggerDisplay(@"\{X = {x} Y = {y}}", Target = typeof(Point))]


Thus, to solve my problem in which I wanted to see the current string in a StringBuilder instance at a glance, I just needed to add the following expression to AutoExp.cs and recompile the source code into AutoExp.dll so the debugger would show it.

[assembly: DebuggerDisplay ( "{m_StringValue}" , Target = typeof ( StringBuilder ) )]


Given how easy it is to add display for types where you don't have source code, I immediately wanted to start adding numerous values to the default AutoExp.cs. However, that would work only for the current user, and I wanted to see if I could get something working globally on the machine so that all accounts using Visual Studio would get the enhanced debugging capability.

A couple of tests later, I saw that every new user account that starts Visual Studio gets the default AutoExp.cs and accompanying AutoExp.dll. Additionally, if there's an AutoExp.dll in the system-wide visualizer directory, Visual_Studio_Install_Directory \Common7\Packages\Debugger\Visualizers, the debugger will aggregate the one in that directory with the default in the C:\Documents and Settings\ user_name\My Documents\Visual Studio 2005\Visualizers directory.

My goal for the book's source code was to provide a new AutoExp.dll that would add all the DebuggerDisplayAttribute values I thought were missing from the FCL. Listing 5-5 shows the values that I thought were missing from the default AutoExp.cs file. This file contains the excellent example of DebuggerTypeProxyAttribute usage for the SecureString type I showed in Listing 5-4. Now you'll be able to see the values of those secure strings right inside the debugger!

Listing 5-5. AutoExp.cs from the book's source code

[View full width]

/*------- ----------- ----------- ------------------------------------------------- * Debugging Microsoft .NET 2.0 Applications * Copyright © 1997-2006 John Robbins -- All rights reserved. ----------------------------------------------- ------------------------------*/ using System; using System.Diagnostics; using System.Text; using System.Text.RegularExpressions; using System.Globalization; using System.Runtime.InteropServices; using System.Threading; using System.IO; using System.Security; using System.Xml; // For Code Analysis/FxCop Rule development. using Microsoft.Cci; using Web = System.Web; using WebCaching = System.Web.Caching; // System. [assembly: DebuggerDisplay ( "IsAlive={IsAlive} TrackResurrection={TrackResurrection}" , Target = typeof ( WeakReference ) )] // System.Diagnostics [assembly: DebuggerDisplay ( "Count={Count}" , Target = typeof ( TraceListenerCollection ) )] [assembly: DebuggerDisplay ( "Name={Name}" , Target = typeof ( TraceListener ) )] [assembly: DebuggerDisplay ( "CounterName={CounterName}" , Target = typeof ( PerformanceCounter ) )] [assembly: DebuggerDisplay ( "Count={Count}" , Target = typeof ( ProcessModuleCollection ) )] [assembly: DebuggerDisplay ( "Arguments={Arguments}" , Target = typeof ( ProcessStartInfo ) )] [assembly: DebuggerDisplay ( "Count={Count}" , Target = typeof ( ProcessThreadCollection ) )] [assembly: DebuggerDisplay ( "Id={Id}" , Target = typeof ( ProcessThread ) )] [assembly: DebuggerDisplay ( "DisplayName={DisplayName} Level={Level}" , Target = typeof ( SourceSwitch ) )] [assembly: DebuggerDisplay ( "Source={Source}" , Target = typeof ( SourceFilter ) )] [assembly: DebuggerDisplay ( "IsRunning={IsRunning}" , Target = typeof ( Stopwatch ) )] // System.Globalization [assembly: DebuggerDisplay ( "Name={Name} ({DisplayName})" , Target = typeof ( CultureInfo ) )] // System.IO [assembly: DebuggerDisplay ( "Name={Name} Position={Position}Length={Length}" , Target = typeof ( FileStream ) )] [assembly: DebuggerDisplay ( "Encoding={CurrentEncoding.EncodingName} " + "EOF={EndOfStream}" , Target = typeof ( StreamReader ) )] // System.Text [assembly: DebuggerDisplay ( "{m_StringValue}" , Target = typeof ( StringBuilder ) )] // System.Text.RegularExpression [assembly: DebuggerDisplay ( "Group Count={Groups.Count}, Text={Value}" , Target = typeof ( Match ) )] // System.Threading [assembly: DebuggerDisplay ( "Name={Name}, Id={ManagedThreadId} " + "AptState={ApartmentState}" , Target = typeof ( Thread ) )] [assembly: DebuggerDisplay ( "IsReaderLockHeld={IsReaderLockHeld} " + "IsWriterLockHeld={IsWriterLockHeld}" , Target = typeof ( ReaderWriterLock ) )] // System.Runtime.InteropServices [assembly: DebuggerDisplay ( "handle={handle} IsClosed={IsClosed} IsInvalid={IsInvalid}" , Target = typeof ( SafeHandle ) )] // System.Web [assembly: DebuggerDisplay ( "IsEnabled={IsEnabled} TraceMode={TraceMode}" , Target = typeof ( Web.TraceContext ) )] // System.Web.Caching [assembly: DebuggerDisplay ( "Count={Count}" , Target = typeof ( WebCaching.Cache ) )] [assembly: DebuggerDisplay ( "UtcLastModified={UtcLastModified}" , Target = typeof ( WebCaching.CacheDependency ) )] // System.Xml [assembly: DebuggerDisplay ( "Count={Count}" , Target = typeof ( XmlAttributeCollection ) )] [assembly: DebuggerDisplay ( "Count={Count}" , Target = typeof ( XmlNodeList ) )] // Code Analysis/FxCop helpers. Thanks to Jim McGregor for most of these. [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( AttributeList ) )] [assembly: DebuggerDisplay ( "{Type.FullName} ExpLength={Expressions.Length} " + "Expressions[0]={Expressions[0]}" , Target = typeof ( AttributeNode ) )] [assembly: DebuggerDisplay ( "Value={Value}" , Target = typeof ( Literal ) )] [assembly: DebuggerDisplay ( "Name={Name} Value={Value}" , Target = typeof ( NamedArgument ) )] [assembly: DebuggerDisplay ( "Type={Type .FullName}" , Target = typeof ( AttributeNode ) )] [assembly: DebuggerDisplay ( "Name={Name} in {Location}" , Target = typeof ( AssemblyNode ) )] [assembly: DebuggerDisplay ( "OpCode={OpCode}" , Target = typeof ( Instruction ) )] [assembly: DebuggerDisplay ( "Count={Count}" , Target = typeof ( TrivialHashtable ))] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( AssemblyNodeList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( AssemblyReferenceList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( AttributeList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( ExceptionHandlerList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( ExpressionList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( FieldList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( InstructionList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( InterfaceList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( MemberList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( MethodList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( ParameterList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( PropertyList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( ResourceList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( SecurityAttributeList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( StatementList ) )] [assembly: DebuggerDisplay ( "Length={Length}" , Target = typeof ( TypeNodeList ) )] // Handles Microsoft.Cci.Local and Microsoft.Cci .Parameter. [assembly: DebuggerDisplay ( "Name={Name}" , Target = typeof ( Variable ) )] // Handles the following Microsoft.Cci types: ArrayType, BoxedTypeExpression, // Class, DelegateNode, EnumNode, FlexArrayTypeExpression, Interface, // InvariantTypeExpression, NonEmptyStreamType, NonNullableTypeExpression, // NonNullTypeExpression, OptionalModifierTypeExrpression, Pointer, Reference, // RequiredModifierType, StreamTypeExpression, Struct, TypeExpression, // TypeIntersectionExpression, TypeModifier, and TypeUnionExpression. [assembly: DebuggerDisplay ( "Name={FullName}" , Target = typeof ( TypeNode ) )] // Handles the following Microsoft.Cci types: Event, Field, Method, Namespace, // Property, TypeMemberSnippet, TypeNode. [assembly: DebuggerDisplay ( "Name={Name}" , Target = typeof ( Member ) )] // System.Security [assembly: DebuggerTypeProxy ( typeof ( AutoExp .SecureStringDebuggerProxy ) , Target = typeof ( SecureString ) )] // Thanks to Habib Haydarien for this. namespace AutoExp { public class SecureStringDebuggerProxy { private string m_value; public SecureStringDebuggerProxy ( System.Security.SecureString s ) { IntPtr ptr = Marshal .SecureStringToBSTR ( s ); this.m_value = Marshal .PtrToStringBSTR ( ptr ); Marshal.FreeBSTR ( ptr ); } public string Value { get { return m_value; } } } }



Although having a new AutoExp.dll is good, I wanted to find a way to install my version as part of WintellectToolsInstall.MSI, included as part of the book's code, without overwriting any customizations you might have done. Because each user had the default version of the file, I thought that that was where someone would have added his own values. Because the global directory visualizer directory does not have an AutoExp.dll in it, I thought that was where I could install my version. However, I didn't want my installation to put my version in that directory and potentially overwrite any existing AutoExp.dll. Consequently, the Wintellect ToolsInstall.MSI performs a check, and if there is an AutoExp.dll in the global visualizer location, I won't install my version. If you do have your own AutoExp.dll, all you'll need to do is copy my definitions out of AutoExp.cs, put them into your own version, and recompile.

Debugger Visualizers

Although the DebuggerDisplayAttribute and friends will go a long way to making your debugging easier, they still have some limitations. Looking at XML data in the Watch window family is something of a pain in the neck. The debugger team heard our cries of lament and created debugger visualizers to help us see our data in a natural form. I know the first time I clicked the magnifying glass next to an XML string and saw the XML data in a natural form was a bit thrilling. If you think I lead a boring life, keep in mind that the first time I demonstrated the XML display at a conference, the audience gave a resounding cheer, and half even gave it a standing ovation!

The default string visualizers for strings, XML, and HTML also provide a way to fix one of the more subtle issues in the Watch window: the value column supports only strings that are 250 characters in length. You'll be able to see unlimited string lengths now.

In addition to the string visualizers, Microsoft also included a very nice database visualizer, the DataSet Visualizer, that handles DataViewManager, DataView, DataTable, and DataSet types. Figure 5-12 shows the viewer with a DataSet. What most developers don't realize about the DataSet Visualizer is that it supports editing the data directly in the visualizer. This is perfect for all sorts of testing while you are debugging. Like any DataGrid, click in the particular cell and start editing away.

Figure 5-12. The DataSet Visualizer


Writing Custom Visualizers

If the String and DataSet visualizers weren't enough, the busy folks on the debugger team did some outstanding work that allows us to write our own visualizers. For years, I've wanted ways to see my application's data in a natural form. Although you can look at an Image type in the Watch window and see all sorts of data, such as the actual pixel format, it'd be much faster if you could simply look at the bitmap in its graphical form.

The debugger team really sat down and thought about how they could make the process as painless as possible, which is actually the most impressive feature about custom visualizers. When it comes to development tools, you can provide all the extensibility you want, but if it's going to take a developer more than fifteen minutes to get started, she'll continue to do the task manually. Whenever you are working on a data structure that's more complicated than one of the collection classes, you need to plan time to write a custom visualizer if it makes sense. Custom visualizers give the debugger an alternative view of the data in an appropriate user interface. The great news is that all you as a visualizer developer have to worry about is the user interface. The debugger will take care of getting the data from the debuggee into the debugger process.

Visualizers are loaded from two directories: the first is the global location in Visual_Studio_Install_Directory \Common7\Packages\Debugger\Visualizers, and the second is the per-user location in C:\Documents and Settings\ user_name\My Documents\Visual Studio 2005\Visualizers. How the debugger knows that an assembly contains a visualizer is if the assembly has at least one assembly-level DebuggerVisualizerAttribute. This attribute tells the debugger the name of the visualizer class itself, the class responsible for marshalling the data from the debuggee to the debugger, the type whose content will be shown, and the description of the visualizer to display in the Watch window. An assembly can have as many custom visualizers in it as you would like.

A visualizer is a simple class derived from the abstract class, DialogDebuggerVisualizer. Your visualizer needs to provide only the Show method implementation that shows your Windows Form with the custom data in it. Passed as parameters to the Show method are the means of retrieving the data from the debugger in addition to a debugger service responsible for coordinating your modal form with the IDE main window.

As the Watch window family is displaying a type, if there's a visualizer for that type, it displays the small magnifying glass icon in the value column. When the user clicks the magnifying glass, the debugger chooses the last visualizer used for that class. As you've seen with the String class visualizer, you can have multiple visualizers per type. Clicking the down arrow next to the magnifying glass lets you pick among the various visualizers for that type.

The description I've just provided is almost as simple as the code. Listing 5-6 shows the code for the StringVisualizer I provide in Wintellect.Visualizers.DLL. Although the built-in String visualizers are excellent for viewing data as a string, XML, or HTML, they don't support editing the data in the form. Because there are many times when I've needed to change a string longer than 250 characters, I needed this visualizer.

Listing 5-6. StringVisualizer.cs

[View full width]

/*------- ----------- ----------- ------------------------------------------------- * Debugging, Testing, and Tuning Microsoft .NET Applications * Copyright © 1997-2006 John Robbins -- All rights reserved. ----------------------------------------------- ------------------------------*/ using System; using System.Diagnostics; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Drawing; using Microsoft.VisualStudio.DebuggerVisualizers; using System.Runtime.InteropServices; [assembly: DebuggerVisualizer ( typeof ( Wintellect .Visualizers.StringVisualizer ) , typeof ( VisualizerObjectSource ) , Target = typeof ( System.String ) , Description = "Wintellect String Visualizer" )] namespace Wintellect.Visualizers { /// <summary> /// A visualizer for <see cref="String"/> classes that provides full editing /// support. /// </summary> public class StringVisualizer : DialogDebuggerVisualizer { /// <summary> /// Shows the form for the <see cref="String"/> visualizer. /// </summary> /// <param name="windowService"> /// An object of type <see cref="IDialogVisualizerService"/> /// that provides methods your visualizer can use to display Windows /// Forms, controls, and dialog boxes. /// </param> /// <param name="objectProvider"> /// An object of type <see cref="IVisualizerObjectProvider"/>. This /// object provides communication from the debugger side of the /// visualizer to the object source /// (<see cref="VisualizerObjectSource"/ >) on the debuggee side. /// </param> protected override void Show ( IDialogVisualizerService windowService , IVisualizerObjectProvider objectProvider ) { // Marshall the string. String dataString = (String)objectProvider.GetObject ( ); Debug.Assert ( null != dataString ); if ( null != dataString ) { using ( StringVisualizerForm form = new StringVisualizerForm ( dataString , objectProvider.IsObjectReplaceable )) { if ( DialogResult.OK == windowService.ShowDialog ( form ) ) { // Just a double check to ensure that the data is // replaceable. if ( true == objectProvider.IsObjectReplaceable ) { // Grab the new data from the form. String data = form .StringData; // Replace it! objectProvider .ReplaceObject ( data ); } } } } } /// <summary> /// Static method for testing the <see cref="StringVisualizer"/>. /// </summary> /// <param name="itemToVisualize"> /// The <see cref="String"/> object to view. /// </param> public static void TestStringVisualizer ( String itemToVisualize ) { VisualizerDevelopmentHost visualizerHost = new VisualizerDevelopmentHost ( itemToVisualize , typeof ( StringVisualizer ) ); visualizerHost.ShowVisualizer ( ); } } }



If you've never looked at the code for a custom visualizer before, you're probably wondering where all the hard work is. Granted, although the code does not show the actual dialog box, what you're seeing is everything you need to do in order to be a visualizer. I told you the debugger team did a great job!

At the top of the file is the assembly-level DebuggerVisualizerAttribute the debugger looks for when it loads a visualizer. The only interesting parameter is the second, which defines the class you want to use for serialization support. As I mentioned a moment ago, I've never found a need to use anything other than the default VisualizerObjectSource class.

The StringVisualizer.Show method is where the most interesting work is for a custom visualizer. The first line that calls the IVisualizerObjectProvider.GetSource method is all that it takes to get your data from the debuggee to the debugger. If you've ever wrestled with any sort of custom marshalling back in the nasty COM days, that single line might have made you want to cry. Once you cast the return value, you're manipulating the object.

After creating the form and showing it through the supplied IDialogVisualizerService interface, I check if the data is replaceable, in other words, changeable just as you were in the Watch window. If the data is replaceable, I get the value from the form and call the IVisualizerObjectProvider.ReplaceObject to change the data in the debuggee. Very little has been mentioned about the fact you can replace data from a custom visualizer, but you can see how trivial it is to do.

As you should deduce from the name of the last method in Listing 5-6, TestStringVisualizer, it has something to do with testing. The two lines of code in there are all that it takes to test a custom visualizer. If you've ever debugged an add-in, you know how painful that can be, but debugging a debugger that's debugging a dialog box is a major pain. The Visual Studio debugger developers correctly realized that they needed a simple way to quickly test a visualizer, so they came up with the VisualizerDevelopmentHost, and those two code lines are all it takes. You can paste the code for the TestStringVisualizer into your custom visualizers, change the name, and change the two parameters to the constructor of VisualizerDevelopmentHost, and you are finished. I think the debugger team deserves a huge round of applause for making visualizer debugging and testing so trivial.

In addition to the StringVisualizer in Wintellect.Visualizers.DLL, I've also included ImageVisualizer that will show you any System.Drawing.Image derived types. Several developers have written open source or free visualizers that I know you'll find extremely helpful. The first is Brett Johnson's outstanding ASP.NET Cache Visualizer, which you can download at http://blog.bretts.net/?p=11. Now you can see what's in your cache and even remove items. I'm so jealous that Brett thought of doing the Cache visualizer before I did. If you do a lot of regular expressions as I do, you'll find that Roy Osherove's RegEx Kit (http://regex.osherove.com/Articles/RegexKit.html) makes looking at the regular expression Match, MatchCollection, and RegEx classes much more informative.

Calling Methods in the Watch Window Family

Something I've found relatively amusing about some developers who have moved to Windows and .NET development from those UNIX operating systems is that they insist that UNIX is better. When I ask why, they indignantly respond in their suspender-wearing glory, "In GDB, you can call a function in the debuggee from the debugger!" I was amazed to learn that operating system evaluations revolved around an arcane debugger feature. Of course, those suspenders snap pretty quickly when I tell them that we've been able to call functions from Microsoft debuggers for years. You might wonder what's so desirable about that. If you think like a debugging guru, however, you'll realize that being able to execute a function within the debugger allows you to fully customize your debugging environment. For example, instead of spending 10 minutes looking at 10 different data structures to ensure data coherency, you can write a function that verifies the data and then call it when you need it mostwhen your application is stopped in the debugger.

Let me give you two examples of code in which I've written methods that I called only from the Watch window. The first example is when I had a data structure that was expandable in the Watch window, but to see all of it I would have been expanding the little plus signs halfway to the planet Eris. By having the debugger-only method, I could more easily see the complete data structure. The second example was when I inherited some code that (don't laugh) had nodes that were shared between a linked list and a binary tree. The code was fragile, and I had to be doubly sure I didn't screw up anything. By having the debugger-only method, I was in essence able to have an assertion function I could use at will.

As you've seen, evaluating, in other words executing, managed code properties is automatic. Calling methods on your object instances or static methods in the Watch window is just like calling them from your code. If the method doesn't take any parameters, simply type the method name and add the open and close parentheses. For example, if your debugging method is MyClass.MyDataCheck ( ), you'd call it in the Watch window with MyClass.MyDataCheck ( ). If your debugging method takes parameters, which you see with the IntelliSense now in the Watch window, just pass them as if you're calling the function normally. If your debugging method returns a value, the Value column in the Watch window displays the return value.

A few rules apply when calling methods from the Watch window. The first rule is that the method should execute in less than 20 seconds because the debugger UI stops responding until the method finishes. After 20 seconds, the Watch window shows "Function evaluation timed out". The good news is that your threads will continue to execute. Once a method has timed out, the debugger correctly remembers that fact for the debugging session and won't reexecute the method unless you click the reevaluate button.

The Watch window isn't the only place where you can call a method; you can also call methods in the Immediate window. Finding the Immediate window is a bit tricky because it's not activated on the View menu as is nearly every other window. It's on the Debug menu, Windows submenu. Alternatively, you can also type immed in the Command window to activate it.

Once in the Immediate window, the one trick you'll have to remember is that to see the value of a variable, you'll need to prefix it with a ? to have the value printed. The ? is just an alias for the Debug.Print command, and it saves you from typing ten characters over and over. Also, the documentation talks about some Immediate windowonly commands, such as .K to show the stack, but those commands are not available in managed-only debugging.

Calling a method in the Immediate window is nearly identical to how you'd do it in the Watch window, except to see the return value, you'd start the expression with ?. For example, if you type ? this., you'll see the same IntelliSense. Once you type the closing parenthesis and press Enter, you've executed the method. It appears so far that the Watch and Immediate windows are doing the same operation when executing a method, but the Immediate window actually has some amazing functionality that's not documented clearly.

If you set a breakpoint in a method and call that method from the Immediate window, you'll see something very interesting: the debugger stops at the breakpoint. For those of you who loved being able to debug a method from the Immediate window, your favorite feature finally came back. This little trick is great for doing a quick test on the code. You'll know that you're in the middle of function evaluation by looking at the Call Stack window because it will show Evaluation of: class.method so you can see where you are in the debuggee. Another neat trick is that if you are not debugging, and the method you want to execute is a static method, you can start the program by calling it in the Watch window.

As if being able to call methods from the Watch and Immediate windows wasn't enough, there's yet another place, which is documented, but I'm willing to bet you've never seen it, the Object Test Bench. This intriguing window is available only at design time, not while debugging. The basic idea of the Object Test Bench window is that it will hold object instances you've created so you can call methods and manipulate them. Debugging a single method called from the Immediate window is useful, but the Object Test Bench allows you to create multiple object scenarios and debug their interactions. Although you won't do the bulk of your testing with the Object Test Bench, it's excellent for those what-if cases in which you want to try something out but don't want to have to write a bunch of code.

To use the Object Test Bench, you first have to create an object. In the Immediate window, you can enter in the appropriate C# or Visual Basic source line you want to execute. For example, executing the following line will open Object Test Bench window as shown in Figure 5-13:

SomethingToDo exampleObject = new SomethingToDo();


Figure 5-13. Object Test Bench window


Once the object is in the Object Test Bench window, right-click the object and select Invoke Method from the shortcut menu. If the method takes parameters, that will bring up the Invoke Method dialog box, into which you'll type the parameters. If the method you're calling has XML documentation comments, you'll even see those in the Invoke Method dialog box. If you've created additional objects shown in the Object Test Bench window, you can pass those instances if they are the valid type. Once you click OK, your method is executed. Just as in the immediate window, if you have a breakpoint set, you'll stop at the breakpoint.

What's especially nice about invoking methods is if the method returns a value, the Object Test Bench shows the Method Call Result dialog box. In the dialog box is a check box, Save Return Value, which allows you to save the value so it appears in the Object Test Bench. Although you might not be able to completely run a large ASP.NET application from the Object Test Bench, you'll be able to do quite a bit of exploration with no code at all.

Once an object is in the Object Test Bench window, you can manipulate the fields in the object through the Immediate window or through a DataTip. Keep in mind that because the Object Test Bench window is available only in design mode, there's no Watch window, or Quick Watch for that matter.

There are two other places where you can create objects for the Object Test Bench window. The first is in the Class View window. If you right-click the class name, the shortcut menu will show two submenus: Create Instance and Invoke Static Method. The Create Instance menu will show all the constructors for the class so you can pick the one you want to use. After selecting the constructor you want to use, the Create Instance dialog box appears so you can assign a name to the instance and fill in the parameters. The Invoke Static Method is analogous, but as the name says, it's only for static methods. The second place is in a Class Diagram for the solution. Because the Class Diagram is just a graphical display of the Class View, the same creation and execution options appear when you right-click the object diagram.




Debugging Microsoft  .NET 2.0 Applications
Debugging Microsoft .NET 2.0 Applications
ISBN: 0735622027
EAN: 2147483647
Year: 2006
Pages: 99
Authors: John Robbins

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