AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1332&pId=-1
Why You Need to Use System.Diagnostics.Debug More Than You Do
page
by Tim Rayburn
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 42285/ 120

Introduction

So if you are like most people I talked to who are professional developers, you might have heard of System.Diagnostics.Debug, but you certainly do not make a habit of using it every day.  While this powerful object has many excellent methods, this article with focus on seven key methods, Assert, Write, WriteLine, WriteIf, WriteLineIf, Indent, Unindent, which you should be using every day in nearly every piece of code you write. 

About System.Diagnostics.Debug

There is one very important thing you need to know about this object and all its methods: You are writing code in invisible ink!  Now I am sure you are going, "that's it, Rayburn has finally gone off the deep end." But hear me out!  Consider Listing 1.

Listing 1 - A simple System.Diagnostics.Debug call

class Program
{
  static void Main(string[] args)
  {
    Debug.Assert(true"Vanishing code!");
  }
}

Simple enough yes?  Well, let us compile this in Debug mode and see what Lutz's Reflector has to say about it.

Listing 2 - Reflector disassembly of Debug mode compile

image 

Yep, just what you would expect.  I am sure now you are totally convinced I have lost it.  But watch, swap this to Release mode and…

Listing 3 - Reflector disassembly of Release mode compile

image

instantly vanishing code!  So the key here is that you can use the Debug object, and all its methods, with reckless abandon and, when you actually compile a release build, all of it goes away.

The Methods

Debug.Assert

While all of the methods we will discuss in this article are important, none will change the way you program as much as Debug.Assert.  The purpose of Assert is to allow you to put in checks for assumed conditions, things which you take for granted at runtime for speed or because you think other pieces of code have handled it for you.  Consider the following simple piece of code in Listing 4.

Listing 4

private string AddNumericStrings(string first, string second)
{
  return (int.Parse(first) + int.Parse(second)).ToString();
}

In this method we are taking two strings, converting them to integers, summing them and returning the results.  Something like this may appear in your code today, and because it is a private helper, you do not worry about checking the parameters because you assume that the public interfaces have done so already.  That is, until you start getting exceptions in testing.

This method is a prime example of the most simple of unstated expectations.  You as the developer expect that you will get strings like "9" and "5," not "Fred" and "George."  You do not want to waste cycles in production checking every time that the developers calling your code have not made a mistake.  Enter Debug.Assert which allows you to check these conditions, but only in Debug mode compiles.  Let us modify the example above with some basic asserts.

Listing 5

private string AddNumericStrings(string first, string second)
{
  Debug.Assert(first != null"The first parameter cannot be null");
  Debug.Assert(second != null"The second parameter cannot be null");
  return (int.Parse(first) + int.Parse(second)).ToString();
}

Now, at least you have ensured you are not going to get a NullReferenceException from this method.  But what happens if a wayward developer makes that mistake and does call this method with something like AddNumericStrings(null,null)?

Listing 6 - Debug.Assert Alert Box

Instantly, our wayward developer gets a dialog box with clear messages from the original developer and a full stack trace. And furthermore, if he clicks Retry, he will be taken directly to the offending line of code.  This is because you, the original developer, have taken the time to explicitly declare your expectations.

Write and WriteLine

Every developer is familiar with Console.Write and Console.WriteLine. These very handy methods output to the Console without and with a new line, respectively, whatever string you may hand them.  Debug.Write and Debug.WriteLine write instead to whomever you have setup to listen. You see, Debug has a property called Listeners which details what sources would like to be notified when Write or WriteLine are called.

There are two ways to add items to the Listeners collection, and they are not mutually exclusive.  The first and most obvious is to simply write some code.  For instance, if you would like all the Debug.Write and Debug.WriteLine statements output to the Console, you could use the following code.

Listing 7 - Add a Console Listener via Code

class Program
{
  static void Main(string[]args)
  {
    Debug.Listeners.Add(new ConsoleTraceListener());
    Debug.WriteLine("Testing 1 2 3...");
  }
}

As you might guess, there are many different trace listeners, including TextWriterTraceListener, which can take any System.IO.Stream and as such could also write to a log file. But as I said, there is another way of controlling what is listening.  The other option is to setup a listener inside the App.Config or Web.Config file of your application.

Listing 8 - Add a ConsoleTraceListener via App.Config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <trace>
      <listeners>
        <add name="MyConsoleListener" 
             type="System.Diagnostics.ConsoleTraceListener" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

Now there is a great deal more to Listeners than we will cover here today; they come in many shapes and sizes and have untapped power we will not discuss.  But this primer will be enough to guide you towards them and let you begin to see output from the commands we are discussing.

WriteIf and WriteLineIf

On the surface these two methods will seem little different from their brothers we just discussed, but in reality they require quite a bit more care in use to be used properly.  In function these methods write a message out to the listeners, but only if a Boolean condition that is provided is true.  This may seem to make both Example A and Example B in Listing 9 the same piece of code, but reality they are very different.

Listing 9

class Program
{
  static void Main(string[]args)
  {
    // Example A
    Debug.WriteLineIf(Convert.ToBoolean(Console.ReadLine()),
      "Complex Condition Met");
 
    // Example B
    if (Convert.ToBoolean(Console.ReadLine()))
      Debug.WriteLine("Complex Condition Met");
  }
}

Example A and Example B will behave identically when compiled in Debug mode, but when compiled in Release mode the difference in Reflector is striking.

Listing 10 - Comparison between Debug and Release mode compiles

As you can see in the second image of Listing 10, the condition of the If statement of Example B has remained in Release mode. This tells us two things, first the need for WriteIf and WriteLineIf is to ensure that even the conditional logic is removed in Release mode, secondly that we should encapsulate all the logic needed for a WriteIf or WriteLineIf into the statement and not create local variables to control them unless those variables are needed for other purposes in the code.

Indent and Unindent

Finally, let us discuss how to keep all of these excellent new messages from becoming a jumbled mess when you need to review the output.  Indent and Unindent are provided by System.Diagnostics.Debug to help keep things organized.  The object will track how many times each method has been called and then add the appropriate indentation to calls to any of the Write methods we have discussed.  Consider the following example.

Listing 11 - Indent and Unindent Demo

class Program
{
  static void Main(string[]args)
  {
    Debug.WriteLine("Starting Main");
    Debug.Indent();
 
    for (int index = 0; index < 10; index++)
    {
      if ((index % 2) == 0)
        Debug.Indent();
      if ((index % 3) == 0)
        Debug.Unindent();
      Debug.WriteLine(string.Format("Loop {0} at Indent Level {1}", index,
        Debug.IndentLevel));
    }
 
    Debug.IndentLevel = 0;
    Debug.WriteLine("Ending Main");
 
  }
}

This code is simply meant to demonstrate how Indent and Unindent work and how they relate to the IndentLevel property of Debug.  If we run the above code, Listing 12 is the result.

Listing 12 - Output

Starting Main
    Loop 0 at Indent Level 1
    Loop 1 at Indent Level 1
        Loop 2 at Indent Level 2
    Loop 3 at Indent Level 1
        Loop 4 at Indent Level 2
        Loop 5 at Indent Level 2
        Loop 6 at Indent Level 2
        Loop 7 at Indent Level 2
            Loop 8 at Indent Level 3
        Loop 9 at Indent Level 2
Ending Main

As you can see this can provide a cleaner output to your logs and allow you to create a visual hierarchy within your logs.  I personally recommend using Indent and Unindent just before and after any looping structure such as for, foreach, while, etc. This will create a clear indication of repetitive tasks in the output.

Conclusion

I hope you can see now how this object and its methods can really help improve your code's traceability and more importantly ensure that your assumptions are documented in code.  The use of Assert to document assumptions and the other methods to provide tracing can really help the next developer understand what you are doing. Because remember, eventually we are all the next developer.

If you are excited about this and ready to give it a try, go for it.  You will also want to spend time with System.Diagnostics.Trace which has many of the same methods as Debug but does not go away when compiled in Release mode.



©Copyright 1998-2014 ASPAlliance.com  |  Page Processed at 9/19/2014 1:51:15 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search