5. Asserts
A trace message can be regarded as a polite progress report; an assert is a loud warning. Traces can give you information that things are going well, or intermediate results so that if things do fail then you can backtrack and find out why; an assert indicates that an important variable is wrong and that the programme has failed. Trace and asserts are clear very different and you should use them differently.
Asserts must only be active in debug code because when they are triggered they indicate that there is a bug, that you guessed that a bug was possible, but all that you did about it was to insert the assert code. It makes your code look very unprofessional if an assert appears in a released product. Furthermore, the assert could generate a modal dialog that blocks the thread.
Asserts are extremely useful in debug code, but they should never be used in released code.
5.1 Windows Error Dialogs
Before explaining asserts I first need to explain some of the error dialogs
that Windows can show. Windows allows you to define a debugger by providing values in the
registry. The system debugger is specified by providing values in the
AeDebug key:
The values that you'll see in this key are shown in the next table.
| Value | Description |
|---|---|
Auto |
When this is set to 1 the debugger is attached
automatically to the faulting process; when it is set to 0 a
dialog is shown asking if the debugger should attach. |
Debugger |
The path and command line to attach the debugger to the process. The parameters are the process ID and the handle of an event that you use to indicate that the process has attached. |
PreVisualStudio7Debugger |
If Visual Studio is installed, this will have the value that
Debugger had before it was changed to invoke the VS debugger |
UserDebuggerHotKey |
The virtual key code of the hot key used to set a breakpoint when a process is running under a debugger |
When the system launches the debugger it will use the launch string as a
C-like format string, so you can use format specifiers in the string to get
the parameters from the system. For example, when you install Visual Studio
the debugger is started with vsjitdebugger.exe -p %ld -e %ld,
here the -p switch is passed an integer that is the process ID of
the process to attach to, the -e switch is passed the handle of
an event which the debugger will set once it has attached to the process.
When an unmanaged application throws an exception the system will
use the values in the
AeDebug key to determine which debugger to invoke and how. However,
Windows has to determine if the debugger should be launched. The
System control panel applet (Control Panel, System)
Advanced tab has an Error Reporting button. This leads to a dialog
that will allow you to determine if error reporting will be used:

If error reporting is disabled (and the But notify me when critical errors occur check box is clear) then you will not see an error dialog when an exception is uncaught. So for this section you must make sure that error reporting is enabled as shown in the screenshot. In this case Windows will look in the following key:
for a value DW0200. This is a path to the error reporting
tool, and usually it is called dw20.exe under the Microsoft's
shared files in Program Files.
A managed application will run under the managed environment, which will handle managed exceptions. The .NET runtime uses the following key to get values to determine how an uncaught exception is handled:
The relevant values in this key are:
| Value | Description |
|---|---|
DbgJITDebugLaunchSetting |
This setting is used to indicate how a managed exception is
handled, a value of 0 means that the unhandled exception dialog
is shown; a value of 1 means that the stack trace is printed
and the process is terminated; a value of 2 means that the
managed debugger is invoked |
DbgManagedDebugger |
The command to start the managed debugger |
The DbgManagedDebugger value will be passed parameters, and
again the string in this value is a C-like format string. There are
four possible values, in this order: the process ID, the ID of the application
domain, a string that is the exception type and a value that is an event
handle which is set by the debugger when the process is aborted.
Try this out. Create a file (ex.cs) with this code:
using System.Diagnostics;
using System.Runtime.InteropServices;
class App
{
[DllImport("kernel32")]
static extern void RaiseException(
uint exceptionCode, uint exceptionFlags, uint numArgs, IntPtr args);
static void Main(string[] args)
{
if (args.Length == 0) return;
switch(args[0][0])
{
case 'e':
throw new Exception("Problem");
case 'd':
Debugger.Break();
break;
case 's':
RaiseException(0, 0, 0, IntPtr.Zero);
break;
default:
break;
}
Console.WriteLine("Completed");
}
}
You have three options. If you pass e as the parameter then a
.NET exception is thrown. If you pass d then the user will be
given the option to attach the debugger. The final option is s
which will raise an unmanaged structured exception (SEH).
Compile this code. Now open regedit, navigate to
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework. and write down the
value of DbgJITDebugLaunchSetting; then navigate to the
Windows NT\CurrentVersion\AeDebug key and write down the value of the
Auto value so that you can restore their
values when you are finished.
Now try all combinations possible for DbgJITDebugLaunchSetting (0, 1,
2) and try each parameter for ex (e,
d, s).
These are the results for a value of 0 for Auto.
DbgJITDebugLaunchSetting | 0 | 1 | 2 |
|---|---|---|---|
| .NET Exception | System error dialog (1), followed by Exception
stack dump |
Stack dump | Attach debugger dialog: attach to debugger or stack dump |
| Attach Debugger | System error dialog, followed by continued execution (2) | Execution continues | Attach debugger dialog: attach to debugger or continue execution |
| SEH Exception | System error dialog (1) followed by SEHException
stack dump |
SEHException stack dump, followed by system
error dialog (3) |
SEHException stack dump, followed by system
error dialog (3) |
Interestingly, if you set Auto to 1 then you get
similar results except that for an SEH exception and a DbgJITDebugLaunchSetting
setting of 1 or 2 you get the 'attach debugger'
dialog (that is, vsjitdebugger). This is as you expect,
Auto is used to determine if the debugger in Debugger is
called and this will only occur for unhandled unmanaged exceptions.
The system error dialog has three types, as indicated in the table above. The first two are essentially the same, the difference is the caption. The first (1) is for an uncaught .NET exception:

Notice the button called Debug. If you click on this button then the debugger will be invoked and attached to the faulting process. The second (2) has a different message indicating that a break point was hit:

The final dialog (3) is for an uncaught SEH exception:

This includes an apology and a hyperlink, and significantly, it does not follow the same Windows theme as the other dialogs, indicating to me that it is being generated by lower level code. If you click on the hyperlink you'll get a brief description of the error condition:

This, too, has a hyperlink to more information:

Finally, the 'attach debugger' dialog (vsjitdebugger) looks
like this:

Note that if you have just the runtime redistributable installed, but not
Visual Studio, then there are no debugger values under the .NETFramework
registry key and the AeDebug key has Auto set to
1 and Debugger set to use drwtsn32.
In this case you will get the dialogs (1), (2) and (3), but you will not have
a Debug button.
Also, note that you might find that in the cases when I indicate that the system error dialog (1) is shown you will see a dialog with more information like the following:

This is the same as the dialog shown above, except that you do not have the
option to get more information. The additional information is
shown if the AllOrNone value is set to 1 in:
At this point, return the registry back to the settings you had before.
5.2 Fail
The Debug and Trace classes have an overloaded
method called Fail which will only be called if the DEBUG
or TRACE symbol is defined, respectively. These will call the
Fail method on every trace listener in the Listeners
collection and, interestingly, if AutoFlush is true the trace
listener's Flush method will be called. Of the two overloads, one
takes a string, and the other takes two strings. The single string is the
failure message, the second string is described as being the detailed message.
The DefaultTraceListener class implements Fail by
writing a stack trace and the message(s) to the output debug string, and if
LogFileName has a value, this data is written to the log file. In
addition, there is an AssertUiEnabled property and if this is set
then a modal dialog is shown on screen with this information. The values of
LogFileName and AssertUiEnabled can be set through
the configuration file. Curiously they have their own section, <assert>,
even though they only refer to instances of the DefaultTraceListener
class.
To try this out, create a file (app.cs)
and add the following code:
using System;
using System.Diagnostics;
class App
{
static void Main()
{
Debug.Fail("Something bad has happened",
"If I knew what the problem was I would tell you");
}
}
Compile this as release mode, that is, do not use the /debug
switch. Run dbgview and then run the process.
The most immediate thing you see is a modal dialog:

This is the standard Win32 MessageBox, this API has limited
options, so the designers decided to chose the dialog with the Abort,
Retry and Ignore buttons. Because these names do no correspond
to the actions that are offered, the caption of the dialog has a handy key:
Abort means that the application will stop immediately; Retry will
mean that the debugger will be started and attached to the process and
Ignore means that the problem will be ignored and the process will
continue executing as before. You'll try out the buttons in the next section
so for now just click on the Abort button whenever you see this dialog.
So why didn't the designers of the framework use Windows Forms to create a
dialog? Well the obvious answer is that this means that any program that uses
Fail will have a dependence upon the Windows Forms library.
However, it is easy to call the Win32 API to create a dialog, I am surprised
that Microsoft didn't do this extra work.
If you take a look at dbgview you will see something like
this:
|
Now compile the process but this time compile a debug build (/debug).
Run the process and you'll see the same assert dialog, but this time you'll
get a bit more information:

The dialog indicates where in the source file where the Fail
was called. The reason is that a stack dump is performed (in this case the
Fail was called in the entry point) using the StackTrace
class and if debugger symbols are available this class will use the symbols to
get source file information.
Now create a configuration file app.exe.config.
<system.diagnostics>
<assert assertuienabled="false"/>
</system.diagnostics>
</configuration>
Run the application again, without compiling. This time you'll see that the
dialog will not be shown, but the message will be shown in dbgview.
As another test, add the following attribute:
Run the application again. This time you'll see that the same information
that is shown by dbgview will appear in the log file.
If you code is a service then it will not have a user interface - if your
service process has a UI you have implemented it wrong and you should start
again, the UI for a service must be a separate process. A service runs
in a different window station to the interactive user, which means that if a
dialog is shown by a service the user will not see it. More worryingly, a
modal dialog will block the thread and since the dialog will not be visible,
and there will not be anyone to click on any of its buttons, it means that the
thread will be permanently blocked. This is why assertuienabled
is so important because it ensures that Fails will still be
reported but the thread will not be blocked. Personally I would have preferred
the dialog to be an opt-in (that is, disabled by default) rather than the
current situation where it is opt-out (it is enabled by default).
Note that Trace.Fail is marked with the [Conditional("TRACE")]
attribute, that is, if you use the Visual Studio projects and you include
Trace.Fail, this method will be called in release mode. Again,
learn this lesson: do not define TRACE for a release build.
Neither TextWriterTraceListener, nor
EventLogTraceListener, nor FileLogTraceListener implements Fail, so if you
use these trace listeners withy code that calls
Debug.Fail or Trace.Fail then you'll get the
implantation provided by TraceListener which will create a
message in the following form:
and call the virtual method WriteLine. So, for TextWriterTraceListener
(or FileLogTraceListener) the string is written to the log file,
and for
EventLogTraceListener the string is written to the event log.
5.3 Assert
The problem with Fail is that it is unequivocal - it is a
failure. An assert is different because it performs a check. There are three
overloads to Assert, in all cases the first parameter is a
Boolean, the other two parameters are a message and a detailed message.
The Assert methods on Debug and Trace
just delegate the work to the TraceInternal class, which merely
checks the condition and on failure calls the appropriate Fail.
For example:
{
if (!condition)
{
TraceInternal.Fail(message, detailMessage);
}
}
It is that simple. So let's try it out. Create a file (app.cs)
and add the following:
using System;
using System.Diagnostics;
class App
{
static void Main(string[] args)
{
Debug.Assert(args.Length > 0, "You must provide arguments");
Console.WriteLine("you passed {0} as the first argument", args[0]);
}
}
Compile and run this application: first, provide one argument and confirm that the application runs as expected; next run it without a parameter to prove that you get the assert dialog with this message:
at App.Main(string[] args);
Notice that unlike the C++ _ASSERTE macro the message does not
contain the expression that failed. That is entirely your responsibility and
perhaps you can use the second string parameter of Assert to
provide this information. If you click on Abort at this point the
process will stop. Don't do that, instead click on Ignore.
at App.Main(String[] args)
What's happened here is that you have not provided any parameters which has
triggered the assert. When you click on Ignore the code continues after
the call to Assert and since the following line attempts to
access the first parameter you get an exception. This is the whole point of
the assert: you know that if the arg array is empty then the
program will not work. The assert flags this up to the tester, essentially the
assert is an immediate indication that there is a condition that will cause
the program to fail. This is of no use at all to your users. It is of immense
use to your testers. Look at the exception stack dump, what does it say?
Compare it to the text in the assert message. Which is the most useful? Assert
messages are more descriptive for the precise reason that you are testing for
a specific condition and you can indicate when that particular condition
fails. Exception messages are usually more generic, which makes them less
useful.
However, even though assert messages are more descriptive than exception
messages you still might not know why a condition failed. Run the process
again with no parameters. Again you see the assert dialog saying that you need
to provide parameters. This time click on the Retry button. Assuming
that you have Visual Studio 2005 and assuming DbgJITDebugLaunchSetting
is set to allow debugger launch (2) the runtime will launch he debugger given
in the DbgManagedDebugger value. This is vsjitdebugger.exe
which gives you the option of launching the actual debugger. You will get the
option of launching the Visual Studio debugger or the CLR debugger (a cut down
version of the Visual Studio debugger provided with the .NET SDK).
vsjitdebugger gives you two check boxes: the first, Set the
currently selecteddebugger as the default will automatically launch the
selected debugger the next time a crash occurs; the second, Manually choose
the debugging engines, refers to the fact that different code requires
different debuggers. If this check box is not selected then the debugger tries
to detect if the code is native, managed or script, if you check the check box
then you get to choose.
The dialog has two buttons, Yes and No. If you chose No then the exception will be returned back to the runtime, which will perform a stack dump of the exception. If you choose Yes then the debugger will be started and it will be attached to the faulting process.
Visual Studio
implements a COM object to allow debugging and vsjitdebugger
creates an instance of this object (and hence, if you choose an new instance
of VS then creating the object will create an instance of VS) and tells the
COM object to attach to the process. At this point you'll see the x86 of the JITted code.
The debugger will be stopped within Debugger.Launch and
so you'll have to move up the call stack to the method that called
Debug.Assert. For example:

If you have symbols for the process (compiled with /debug)
then the disassembly window will show source code and x86 code, and the
debugger will open the source file, so that you can single step through the
source code.
If you want to perform the same action yourself, that is attach the
debugger to a running application, you can invoke the JIT debugger by passing
the process ID (from task manager, for example) through the /p
switch to vsjitdebugger.
5.4 What Should You Assert?
The first thing to be aware of, is that Assert is conditional, which means
that it will not be compiled if the appropriate symbol is not defined. What is
not so obvious is that you should not call any code as a parameter to a
conditional method that might change state. This is because if the conditional
method is not called then neither are any properties or methods that you pass
as parameters. For example, create a file (app.cs) and add this
code:
using System.Diagnostics;
class App
{
static void Main(string[] args)
{
Data data = new Data(args.Length > 0 ? args[0] : null);
data.DoSomething();
}
}
class Data
{
string str;
public Data(string s)
{
str = s;
}
public void DoSomething()
{
Debug.Assert(IsDataCorrect, "Internal state inconsistent");
Console.WriteLine("Done something with {0}", str);
}
public bool IsDataCorrect
{
get
{
if (str == null) return false;
if (str.Length == 0) return false;
return true;
}
}
}
Here, the number of parameters is tested and if there are parameters then
the first one is passed to the Data constructor, otherwise
null is passed. The Data class has a property called IsDataCorrect
that determines if the internal state of the method is correct. If it is not
correct it will return false. This property is called in the
Assert so that if the internal state of the object is not correct
then the developer will be informed during debugging.
Compile it defining DEBUG:
Now run this code with a single parameter, you'll see that it works as expected. Run it
without a parameter and you'll find that an assert dialog will be shown. Click
on Abort. Now imagine that the developer of Data wants to
takes steps to ensure that instances are correct. He might decide to do this:
{
get
{
if (str == null)
{
Console.Write("Please give a value: ");
str = Console.ReadLine();
}
if (str.Length == 0) return false;
return true;
}
}
Compile this code as before. Run it with a parameter and then without a parameter; when you are asked for a value type something and press Enter. In both cases you'll find that the program will work correctly, it will give a message that it has done something with the data that you provided. Here's the result from the last test:
give a value: test
Done something with test
Now run it again without a parameter, and when asked for a value just press
Enter. You'll see that an assert dialog will appear, as expected. (I
find that in this case the assert dialog will not be topmost, however, you will see
that it appears on Windows' task bar.) Abort the program. The program appears to
work as expected. However, try compiling for release mode, that is, do not
define DEBUG on the command line. Run the process without a parameter.
What do you
see? Here's the results I get:
Done something with
Notice that you are not prompted to enter a value. The reason is that the
entire line that calls the Assert is not compiled, including the
call to the IsDataCorrect property.
The lesson to learn from this example is that whatever you call (method or property) as a parameter to a conditional method should not change the state of any objects in your application. If they do have such side effects it means that your code will run differently in release mode than it does in debug mode.
Back to the topic of this section: what should you assert? Let's start with a few rules about assertions and then I'll go into details afterwards
- asserts are used to find implementation errors
- asserts are not a replacement for defensive programming
- use asserts to make bugs reveal themselves
- beware of side effects
- use asserts to check consistency
- do not use asserts to validate user input
Defensive programming ensures that errors are handled accordingly,
asserts go hand-in-hand with defensive programming, but is not a
replacement. The reason is that asserts are a debugging tool and should be
used to get bugs to reveal themselves, and not to handle bugs. If a method
takes a string parameter and requires that the parameter is not null
then an assert will reveal a null parameter during debugging
and allow you to pay more attention to where that parameter came from and
hence make sure that a null parameter cannot be passed in
future. At runtime, for a release build, the asserts will not be active (and
should not be active) and so this check is not performed. Such a
null parameter still has to be handled in a release build, or else it
could lead to invalid results or to an exception.
Asserts are used to detect a condition that should always be true if the program is running correctly. Of course, the reverse is not the case, if asserted conditions are true it does not necessarily mean that the program is running correctly. It is possible for asserts to detect if the program is not running correctly, but this depends on how many asserts you have. It makes little sense to assert everything, because that would make the code unreadable. Further, you should not assert conditions that you think could fail because of a possible bug. If you think that there is a possible bug then you should take steps to fix the bug, in which case the assert is unnecessary. Thus, a balance must be struck to perform enough tests, but not to perform too many.
There are essentially two levels of conditions that you should check, which I will call major conditions and minor conditions. The major conditions are data consistency (class invariants and loop invariants) and validating method parameters. Class invariants are conditions that must be true for an instance of the class to be valid, similarly, loop invariants are conditions that must be true before the loop starts and true at the bottom of the loop code (ie before the loop terminates, or before the next iteration).
For example, a date class will hold data for the day, month and year. The month should be between 1 and 12 and the day should be between 1 and 31. For example:
{
int day, month, year;
// other code
[Conditional("DEBUG")]
private void AssertValid()
{
Debug.Assert(day >= 1 && day <= 31, "Day is out of range 1 <= day <= 32");
Debug.Assert(month >= 1 && month <= 12, "Month is out of range 1 <= month <= 12");
}
}
In this case the AssertValid method checks to see if the
instance is valid, and performs two asserts. The method is marked as
conditional because it will be empty if compiled in release mode. (The astute
amongst you will notice that these conditions are too simplistic, because not
every month has 31 days, and February has 28 or 29 depending on whether the
year is a leap year. For simplicity's sake, I will not add those checks.)
This method checks that the object is valid, it does not necessarily check
that an operation is valid. So, if a Date instance is initialized
to December the 20th and you call a member function to increment by twelve
days the assertion will show a bug if the operation results in a date of
December the 32nd. The assertion will not detect if the operation of adding
twelve days resulted in January 2nd because that is a valid date.
Class invariants are so named because when an operation is carried out on an instance the conditions will still remain true. Such operations are necessarily public operations (methods or property accesses). Private and protected members can only be called by code internal to the class or derived classes, and ultimately they will be called by public members. However, they will be called within those public members when the object will be in a state of change and hence invariants cannot be true. For example:
{
AssertValid();
day += days;
int daysInMonth = DaysInMonth();
while (days > daysInMonth)
{
days -= daysInMonth;
month++;
if (month > 12) month = 1;
daysInMonth = DaysInMonth();
}
AssertValid();
}
private static int[] days = new int[]{31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 30};
private int DaysInMonth()
{
Debug.Assert(month >= 1 && month <= 12, "month is out of range");
if (month == 2) return DaysInFebThisYear();
return days[month - 1];
}
The public method Increment first checks that the object is valid and then
performs the increment routine by simply adding the required number of days to
the day member and then if this is an invalid value it decrements the number
of days in this month and then moves to the next month. The object will be
invalid during this routine, so the private methods DaysInMonth and DaysInFebThisYear must not call
AssertValid. However, the DaysInMonth method assumes that
the month value is between 1 and 12, and so an
assert is performed on this condition. This shows that although the object as
a whole is invalid, parts of it should be valid for specific routines to work
correctly.
Loop invariants are not quite as obvious as class invariants. A loop
invariant is a condition that is true before the loop starts and is true when
an iteration of the loop has completed (but may not be true during the
iteration). The loop test condition is not part of the loop invariants, the
test condition simply determines if another loop should be performed. For
example, a routine that determines the smallest value in an array will have a
condition of position < data.Length as the loop check (assuming
position is the current index and data is the array), but the
loop invariants will be the following (assuming indexOfSmallest
is the index of the smallest element found so far):
indexOfSmallestis a value between0andposition- for every index between
0andpositionthe value in the array is greater or equal to the value atindexOfSmallest
Thus:
{
int position = 0;
int indexOfSmallest = 0;
for (; position < data.Length; position++)
{
if (data[position] < data[indexOfSmallest)
indexOfSmallest = position;
}
return indexOfSmallest;
}
In this simple example it is not necessary to perform the checks on the loop invariants.
The other thing that you can assert are parameters, however, you should be
wary of asserting parameters of public methods. Asserting parameters of
private methods is fine, because an invalid parameter will be caused by a bug
in the current class. The parameters of a public method will be passed by
another class and as such do not represent a bug in the current class, it
represents a bug in the calling class. Thus, you should throw an ArgumentException as part of your
defensive programming to indicate that the calling code is bad. However, you may
also choose to fail an assert as well to aid your testing:
{
if (month < 1 || month > 12)
{
Debug.Fail("month is out of range");
throw new ArgumentException("month is out of range");
}
this.Month = month;
if (day < 1 || day > DaysInMonth())
{
Debug.Fail("day is out of range");
throw new ArgumentException("day is out of range");
}
this.day = day;
this.year = year;
}
Note that since the error handling in this code already performs a check
there is no point in calling Assert, all you want to do is to
inform the user, and so Fail is called. The defensive programming
is far more important than the assert, so make sure that you write the error
handling code before you write any asserting code.
That covers the more important assertions, now I want to cover assertions that I regard as being less important. For example, it can be useful to assert return values, but in general your error handling should ensure that 'correct' and expected values are returned. It is more useful to assert loop counters, particularly when the the limit of the counter is determined programmatically. This allows you to pick up on situations like overflows which could cause the loop to be performed far more times than you intend. Another thing to assert is impossible values in a switch.
Finally, it is worth pointing out that although you can assert conditions
in services you must not perform UI asserts. A service runs under a
different security content to the interactive user and hence any 'user
interface' used by the service will be on a different desktop to the user, a
desktop that is not visible to the user. (If the service process has user
interface that is visible to the interactive user then it should be through a
process running in the user's security context. The Allow service to
interact with desktop setting is evil, do not use it. If you are tempted
to use it, you open a security hole and deserve all that happens to your
process.) The user will not see any dialogs generated by a service, moreover,
a modal dialog will block the thread with no possibility of anyone dismissing
it. You could depend on the configuration file setting assertuienabled
to be false, but this means that someone may set it to true. Instead, you should
disable this ability programmatically with code like this in the service
entry point:
{
DefaultTraceListener dtl = tl as DefaultTraceListener;
if (dtl != null) dtl.AssertUiEnabled = false;
}
This ensures that if the user decides to add a DefaultTraceListener
to the Listeners collection then the object is configured
correctly. This is such an obvious action that I am surprised that the
framework library does not do this as part of the ServiceBase class.
| I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com. |
Errata
If you see an error on this page, please contact me and I will fix the problem.