The framework includes an entire namespace and a set of classes for
debugging. In many ways it provides a lot of support for debugging, but it is
still lacking in the basics.
This article focuses on the Assert dialog, probably the most important
feature provided by the debug classes. Unfortunately, the assert dialog is very
weak. In this article, I will discuss how to improve the Assert dialog by taking
advantage of some of the customization features offered by the framework.
In addition, I have included an enhanced ready-to-use Assert dialog box that
you can use immediately in your programs to improve your debugging
The Standard Assert Dialog
I will begin by discussing the default dialog box and then follow that
discussion with my enhanced replacement.
The default assert dialog box looks like the following.
It displays any text from an
Debug.Assert(expr, message) or
Debug.Fail(message) console. Unfortunately, if you do not specify a
message in the
Debug.Assert(expr) statement, the debugger has no
message and merely reports just the filename and line number, so it is not
always clear what was triggered.
The default assert also provides the oddly named "abort," "retry" and
"ignore" buttons. Retry invokes the debugger, abort exits the application and
ignore continues execution from the point of the assert.
Even though, the framework provides a number of debugging features, it falls
short of the debugging experience that I have had with C++, pre .NET. For one
thing, I could be in a loop in which the assert continually fires ad infinitum,
and have no way to bypass this specific assert.
Secondly, I have to prepare a message for each assert that I write, or I will
not have a descriptive message to view when the assert displays, but instead of
line number, which is meaningless to me until I go into the debugger. Because
about a large percentage of the lines of code I write consists of asserts, this
quickly becomes very cumbersome; I also wonder if the strings themselves will be
carried over into my release application, thereby bloating the size of my
application or making it easier for someone to deobfuscate my code.
Another pet peeve is the varying size of the dialog box, if the file path is
too long, I have a wide dialog; if I am deep into a recursive function, I get a
tall dialog. In either case, it obscures what is underneath and causes repaint
events to be emitted.
Now let's look at my enhanced assert dialog box. This is my
SuperAsserter dialog with a custom assert message "Testing".
Notice there are four buttons below. "Retry" is appropriately renamed
"Debug," and we have a new button called "Ignore Always," which forces the
assert dialog box to remain hidden upon each invocation of any troublesome
The window always remains the same size, regardless of the size of the stack
trace, but it can be resized to suit the developers needs. The stack trace is
kept in a scrolling textbox, whose contents can be copied, if need be. The label
above the textbox indicates the most important information about the assert in
large bolded text, the problem method, file, line, number and any custom
Debug.Assert does not contain a custom message, a line is
read in directly from the source file and line number, if found; and the text of
the line is cached in memory for subsequent invocations of the same assert. The
line of text is used in placed of the empty custom message. The following dialog
box is then produced.
There are a number of ways that this assert dialog box can be improved, but,
for now, it serves my needs.
- Adding a button to "Save asserts to a file."
- Ignore all asserts in the method, class, file, or stack trace containing the
- Reporting other information such as the Win32 last error message and son on.
I had also planned to produce another
SuperCatcher dialog box
that can be displayed when an exception is fired. In this case, the dialog box
would show all inner exceptions and display the line of code that trigger each
exception as well as the message, type and properties of each exception.
SuperAsserter can be used in both console and windows
applications. With console applications, it is necessary to include references
because the dialog box is a
SuperAsserter do either of the follow:
- call the
Setup method in
- add the
SuperAsserter singleton class instance to either
Debug.Listeners or Trace.Listeners
Default is the name of the default listener. If you don't remove it, you will
get two dialogs per assert--the new and the old. You can also remove all
listeners by calling
Debug and Tracing Basics
The primary location of debugging support in .NET is through
The first two classes you should know are
System.Trace, which are virtually identical, except for the fact
that method calls of
System.Debug are omitted in the release
builds, whereas they are both present for
More precisely, the
System.Debug class has an attribute
[ConditionalAttribute("DEBUG")] attached. This is another feature
supported by the compiler to aid debugging. Any method with has a
Conditional attribute applied will not be called when the
preprocessor symbol has not been defined. Defines can set through the C#
compiler switch for defines (example, in
/d:DEBUG or /d:TRACE), or
project options. You can also declare defines in the top of a file--but it has
to be written before any code.
Of course, the preprocessors
#if DEBUG and
can also be used to omit debug. The conditional attribute was absolutely
necessary for C# and VB.NET because those languages have limited preprocessor
support, unlike C++. There would be no other way for
to be eliminated in the release step, without bracketing the line of code with
. Note that the CLR does not require a language to honor the
System.Trace have several
|By default, produces a dialog box indicating the file and line and stack
trace with the abort, retry and fail options.|
|Calls Fail if expression is false.|
|Calls Fail if expression is false, with message being the empty
|By default, emits the string to the Output window in the debugger.
Internally, this calls the Win32 API function
writeline if the condition is true. Similar to, if
The descriptions above indicate the behavior of the
DefaultTraceListener that is attached to the
collection of both the
Trace class. There
are other types of listeners that could be attached such as a
TraceListener that produces event log output and another that
writes out to a stream.
There are two other provided
. To get a listener that writes out
to stdout or stderr, use
new TextWriterTraceList(Console.Out or
can be replaced by another listener by the following approaches.
To remove the default
To remove all existing listener, call
To add a new listener, call
Debug.Listeners.Add( new TextWriterTraceListener(Console.Error) );
DefaultTraceListener invokes a message box when
Debug.Assert or Debug.Fail is called, and the
WriteLine commands outputs to the Debugger Output window. This
Trace class, calling the
command may produce a message box in the shipped product.
SuperAsserter, I constructed a new class derived from
I then overrode the
void Fail(string message, string
method. This method is automatically called by
, if the expression passed to it is false.
Fail calls an Windows Form class called
that implements the actual dialog.
Control is returned to
Fail when the dialog is closed. If the
user chose "Debug",
Debugger.Break. It was
necessary to add the attribute
DebuggerHiddenAttribute applied to
Fail method, because not doing so will cause the debugger to
break inside the Fail method instead of the code that we are interested in--the
method that invoked
Assert in the first place.
This represents just one of my articles in the debugging series. There will
be others. I'd appreciate your vote; it is a powerful motivating force for
|June 28, 2003||Original article.|