Defensive Programming in C# – Part 1

The .NET runtime provides a few facilities for making programming less dangerous. You can use conditional methods and tracing to add checks and log code to an application, to catch errors during development, and to diagnose errors in released code.

1. Conditional Methods

Conditional methods are typically used to write code that performs operations only when compiled in a certain way. This often takes place in order to add code that’s called only when a debug build is made and not when called in other builds, usually because the additional check is too slow.

In C++, you’d do this by using a macro in the include file that changes a function call to nothing if the debug symbol isn’t defined. This doesn’t work in C#, however, because there’s no include file or macro.

In C#, you can mark a method with the Conditional attribute to indicate when calls to it should be generated. For example:

using System;

using System.Diagnostics;

class MyClass {

public MyClass(int i)

{

this.i = i;

}

[Conditional(“DEBUG”)] public void VerifyState()

{

if (i != 0)

Console.WriteLine(“Bad State”);

int i = 0;

}

class Test {

public static void Main()

{

MyClass c = new MyClass(1);

c.VerifyState();

}

}

The VerifyState() function has the Conditional attribute applied to it, with DEBUG as the conditional string. When the compiler comes across a function call to such a function, it looks to see if the conditional string has been defined. If it hasn’t been defined, the call to the function is eliminated.

If this code is compiled using /D:DEBUG on the command line, it will print Bad State when it runs. If it’s compiled without DEBUG defined, the function won’t be called, and there will be no output.

2. Debug and Trace Classes

The .NET runtime has generalized this concept by providing the Debug and Trace classes in the System.Diagnostics namespace. These classes implement the same functionality but have slightly different uses. Code that uses the Trace classes is intended to be present in released software, and therefore it’s important not to overuse it, as it could affect performance.

Debug, on the other hand, isn’t going to be present in the released software; therefore, you can use it more liberally.

Calls to Debug are conditional on DEBUG being defined, and calls to Trace are conditional on TRACE being defined. By default, the Visual Studio IDE will define TRACE on both debug and retail builds and will define DEBUG only on debug builds. When compiling from the command line, you’ll need the appropriate option.

In the remainder of this chapter, examples that use Debug also work with Trace.

3. Asserts

An assert is simply a statement of a condition that should be true, followed by some text to output if it’s false. You could write the preceding code example better as this:

// compile with: csc /r:system.dll file_1.cs

using System;

using System.Diagnostics;

class MyClass {

public MyClass(int i)

{

this.i = i;

}

[Conditional(“DEBUG”)]

public void VerifyState()

{

Debug.Assert(i == 0, “Bad State”);

}

int i = 0;

}

class Test {

public static void Main()

{

Debug.Listeners.Clear();

Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));

MyClass c = new MyClass(1);

c.VerifyState();

}

}

By default, asserts and other debug output are sent to all the listeners in the Debug.Listeners collection. Since the default behavior is to open a dialog box, the code in Main() clears the Listeners collection and then adds a new listener that’s hooked up to Console.Out. This results in the output going to the console.

Asserts are hugely useful in complex projects to ensure that expected conditions are true.

4. Debug and Trace Output

In addition to using asserts, you can use the Debug and Trace classes to send useful information to the current debug or trace listeners. This is a useful adjunct to running in the debugger, because it’s less intrusive and can be enabled in released builds to generate log files.

The Write() and WriteLine() functions send output to the current listeners. These are useful in debugging but not really useful in released software, since it’s rare to want to log something all the time.

The WriteIf() and WriteLineIf() functions send output only if the first parameter is true. This allows the behavior to be controlled by a static variable in the class, which you could change at runtime to control the amount of logging that’s performed. For example:

// compile with: csc /r:system.dll file_l.cs using System;

using System.Diagnostics;

class MyClass {

public MyClass(int i)

{

this.i = i;

}

[Conditional(“DEBUG”)] public void VerifyState()

{

Debug.WriteLineIf(debugOutput, “In VerifyState”);

Debug.Assert(i == 0, “Bad State”);

}

static public bool DebugOutput {

get

{

return(debugOutput);

}

set

{

debugOutput = value;

}

}

int i = 0;

static bool debugOutput = false;

}

class Test {

public static void Main()

{

Debug.Listeners.Clear();

Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));

MyClass c = new MyClass(1);

c.VerifyState();

MyClass.DebugOutput = true;

c.VerifyState();

}

}

 

This code produces the following output:

Fail: Bad State

In VerifyState

Fail: Bad State

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *