Debugging and Tracing in C#


This article has been excerpted from book "The Complete Visual C# Programmer's Guide" from the Authors of C# Corner.

Among the many diagnostic classes the .NET Framework provides are the Debug and Trace classes. The Debug class helps us debug code, and the Trace class helps us trace the execution of code. The Debug class is intended for debug builds, and the Trace class is used for release builds. 

Table 21.3 describe the members of the Debug and Trace classes. 

table21.3.gif

Table 21.3: Debug/Trace Class Members 

You must define the DEBUG and TRACE compiler directives to activate debugging and tracing respectively. If you neglect to define either of these directives, Trace or Debug calls will be ignored during compilation. You can define these in Visual Studio .NET by choosing Properties from the Project menu. 

It is generally recommended that you define TRACE in your code, since at a minimum, end users and administrators may turn on lightweight diagnostic tracing out in the field when they encounter a problem related to the application. Microsoft tends to deliver both versions of its applications, release and checked (or the debug) build. The checked build versions of commercial applications are larger in size and slower in performance than the release build versions, because they perform verbose and extensive logging to the computer disk to help you find the culprits or bugs in support issues. Both the Debug and Trace classes contain the exact same methods. But unlike Debug, Trace is meant for monitoring the application's well-being in the field. Even if you debugged things in your testing phase, you may find it difficult to find some of the fuzzy problems, particularly in distributed n-tier environments. For example, some types of problems arise only when the application is under an intense load, or the problems may occur randomly or intermittently. Tracing may prove more useful in finding these problems.

Although Debug and Trace have identical members, you should use Debug during development and Trace for diagnosing an application after deployment. The output methods of the Trace and Debug classes are Write(), WriteIf(), WriteLine(), and WriteLineIf(). WriteLine puts a carriage return and line feed (CRLF) on the end of the output, but Write does not. WriteIf and WriteLineIf are similar to Write and WriteLine respectively, but they provide conditional tracing capabilities since they trace only if the first parameter evaluates to true. The Indent() and UnIndent() methods of the Debug and Trace classes increase and decrease the current IndentLevel, which determines how much the output will be indented by one, respectively. The Fail() method of the Trace and Debug classes produces an error message so you can handle exceptions and errors during debugging more easily. The Flush() method of the Trace and Debug classes forces buffered data to pour to the Listeners collection immediately. We will talk about listeners later. The Close() method of the Trace and Debug classes performs a Flush() implicitly and then closes the Listeners. The Assert() method of the Trace and Debug classes evaluates the passed condition, then if the condition is false, it pours out its diagnostics messages into the Listeners collection. The Assert method has three overloads, as shown in Listing 21.5. 

Listing 21.5: Assert Overloads 

           // Assert without description
            Debug.Assert(a > b);

            // Assert with description
            Debug.Assert(a > b, "a is not greater than b");

            // Assert with description and more detail
            Debug.Assert(x > y, " x is not greater than y", "Assertion... y is less than x");

Although there are many debug and trace methods, when in the past you used Microsoft Foundation Classes in Visual Studio 6, Microsoft made extensive use of methods similar to the Debug.Assert() and Trace.WriteLine() methods. You should consider that an implicit, but strong, recommendation for your code. You can enable debugging or tracing by adding a #define DEBUG or #define TRACE line to the top of your code or using the /d:DEBUG or /d:TRACE compiler switch when you compile. See the example in Listing 21.7. 

The C# .NET compiler provides the following techniques to define compilation variables:

  • Compiler command-line switches. For example, /define:DEBUG or /d:DEBUG; /define:TRACE or /d:TRACE.
  • Environment variables in the operating system shell. For example, SET DEBUG=1, SET TRACE=1.
  • Pragmas in the source code. For example, #define DEBUG to define the compilation variable or #undef DEBUG to undefine it; #define TRACE to define the compilation variable or #undef TRACE to undefine it.

Autoflush is used to automatically flush the output buffer after each write to the listeners if set to true. In the code you can enable it with a Debug.AutoFlush = true; statement. 

IndentSize is the number of spaces in an indent, with the default being 4. The IndentLevel property and Indent() methods indent the content IndentSize times any and 1 respectively. In the code you can enable it with a Debug.IndentSize = 6; statement. 

Other than programmatic options, you can change your <Application>.EXE.CONFIG application configuration file such as in Listing 21.6 to set the AutoFlush and IndentSize properties. 

Listing 21.6: Configuration file EXE.CONFIG 

<configuration>
  <system.diagnostics>
    <debug autoflush="true" indentsize="6"/>
    <listeners>
    </listeners>
    </debug>
    <trace autoflush="true" indentsize="6"/>
    <listeners>
    </listeners>
    </trace
  </system.diagnostics>
</configuration>

The sample code in Listing 21.7 uses the configuration file shown in Listing 21.6 and outputs with Debug and Trace statements to the console. 

Please note the following. Open the Debug Monitor utility (DbMon.exe) of the Platform SDK for Windows NT, 2000, and XP in a separate command prompt console before running the listing. DbMon.exe will show any debug and trace messages coming from any application running on your system that are sent to DefaultTraceListener (OutputDebugString application programming interface [API] method of the Windows operating system). We will talk about TraceListener classes later. For now just know that Debug Monitor runs in its own console window and displays messages sent by your application using the OutputDebugString function. If you do not have or do not want to use DbMon.exe, uncomment the two following code lines to send debug and trace output to the console: 

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

Listing 21.7: Using Debug and Trace 

#define DEBUG // for debugging
//or
#define TRACE // for tracing

using System;
using System.IO;
using System.Diagnostics;

public class TraceListenerExample
{
    public static void Main()
    {
        // If you do not have DBMON.EXE, then to watch debug and trace messages,
        // make sure that the system will output debug and trace to the console.
        // Otherwise you cannot monitor Debug and Trace messages.
        // The DefaultTraceListener will output
        // to OutputDebugString of Windows operating systems.
        // We will talk about TraceListener classes later.
        // Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));
        // Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
        // Indent by one IndentSize times 1. Indent this time 6 x 1 = 6!

        Debug.Indent();
        Trace.Indent();
        Debug.WriteLine("Debugged 1!");
        Trace.WriteLine("Traced 1!");
        bool bDebugTrace = false;

        // test for a boolean flag to output debug or trace.
        Debug.WriteLineIf(bDebugTrace, "Not Debugged 1!");
        Trace.WriteLineIf(bDebugTrace, "Not Traced 1!");
        bDebugTrace = true;

        // test for a boolean flag to output debug or trace.
        Debug.WriteLineIf(bDebugTrace, "Debugged 2!");
        Trace.WriteLineIf(bDebugTrace, "Traced 2!");

        // this is faster than WriteLineIf! So prefer the block below.
        if (bDebugTrace == true)
        {
            Debug.WriteLine("Debugged 3!");
            Trace.WriteLine("Traced 3!");
        }
        Console.Read();
    }
}

Conditional Debug Attributes 

You can use a conditional attribute in front of the method declaration so your method is callable when the condition is true. Conditional attributes, like other C# attributes, do not put any burden on the code calling them. For example, in Listing 21.8, if you put #define IwantDEBUG at the top of your code or compile your code with the /D:IwantDEBUG option, then myMethod can be called. Otherwise, the Microsoft intermediate language code for the myMethod code will be omitted (not generated) in your final assembly. 

Listing 21.8: Conditional Attribute Example 1 

using System;
using System.Diagnostics;

class Test
{
    [Conditional("IwantDEBUG")]
    public static void myMethod()
    {
        Console.WriteLine("only for conditional DEBUG...");
    }

    public static void Main()
    {
        myMethod();
    }
}

Listing 21.9 is a more sophisticated example of using conditional attributes. 

Listing 21.9: Conditional Attribute Example 2 (debug2.cs) 

using System;
using System.Diagnostics;

public class Debugging
{
    private int r = 0;
    public Debugging(int r)
    {
        this.r = r;
    }
    public int R
    {
        get { return r; }
        set { r = value; }
    }

    public static void Main()
    {
        methodA();
        methodB();
        methodC();
        methodD();
        methodE();
        methodF();
    }

    public static void methodA()
    {
        Console.WriteLine("Method A");
    }

    [Conditional("saygin")]

    public static void methodB()
    {
        Console.WriteLine("Method B - has [conditional(saygin)]");
    }

    public static void methodC()
    {
        Console.WriteLine("Method C");
    }

    public static void methodD()
    {
        Console.WriteLine("Method D");
    }

    [Conditional("alex")]

    public static void methodE()
    {
        Console.WriteLine("Method E - has [conditional(alex)]");
    }

    [Conditional("saygin"), Conditional("alex")]
    public static void methodF()
    {
        Console.WriteLine("Method F - has [conditional(saygin),conditional(alex)]");
    }
}

Conclusion

Hope this article would have helped you in understanding the Debugging and Tracing in C#. See other articles on the website on .NET and C#.

visual C-sharp.jpg
The Complete Visual C# Programmer's Guide covers most of the major components that make up C# and the .net environment. The book is geared toward the intermediate programmer, but contains enough material to satisfy the advanced developer.

Up Next
    Ebook Download
    View all
    Learn
    View all