Whammy Tracing: Hassle-Free .NET Debugging

When I write an application with 40 or 50 thousand lines of code, its major capabilities can be tedious to debug line by line. I’d rather run these features at full speed and then go back and audit what actually occurred. The debugging tool that this article discusses, called the Whammy, provides an easy way to do that.

In a very unobtrusive way, the Whammy permits you to use the .NET Framework to add detailed tracing information to your application while writing very little code. For example, if you wrote code like the following:

System.Diagnostics.Trace.WriteLine(string.Format(
   "Main called at {0}", DateTime.Now))

The Whammy is for you. It achieves effectively the same result, but with a lot less typing (as I have implemented it):

Whammy.ConsoleWriteWithTimestamp()

Author Note: Whammy comes from the idea that this code automatically answers the question ‘who am I‘ about the calling method. Pronounced fast as one word, Who-Am-I becomes Whammy. That’s my story and I am sticking to it.

By reading this article, you will learn about the Whammy and its corresponding technologies, ConditionalAttribute, the StackFrame and StackTrace objects, and reflection.

Using the ConditionalAttribute to Remove Deployed Code

You can apply the ConditionalAttribute, which takes a string argument, to a class or method. The string or conditional argument, if defined, permits calls to the conditional classes or methods. Code tagged with the conditional attribute is always emitted to MSIL (intermediate language code), but if the string is not defined, those calls are ignored.

To define a conditional string constant, use the #define pragma, as in the following example:

#define "MY_STRING"

Obtaining a StackFrame and StackTrace Object

Two of the many interesting features that the System.Diagnostics namespace defines are the StackTrace and the StackFrame. The StackTrace is an ordered collection of StackFrames; a StackFrame is all of the information about a single, literal call stack. StackFrames include information about the method called, arguments passed to that method, and from there, all kinds of information that you can glean through reflection.

Using Reflection to Obtain Method Information

Reflection is a .NET technology that evolved from run time type information (RTTI). It allows you to explore the .NET metamodel. Reflection supports a very dynamic kind of programming that enables programmers to receive an object blindly and then ask about its methods, fields, properties, and events. You can even invoke these operations or dynamically invoke methods without knowing what they are before hand.

Reflection is a very useful technology that you can use to dynamically resolve the namespace and name of a calling method (the following Whammy class listing shows how). You also could expand the details of the reflected type and make the Whammy output more verbose:

Listing 1: The Whammy Class Automatically Provides Trace Information about the Calling Method

Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Text

Module Module1

   Sub Main()
      Whammy.ConsoleWrite()
      Console.ReadLine()
      System.Diagnostics.Trace.WriteLine("Main called")

   End Sub

End Module

' Thanks to Bill Wagner and Addison Wesley for the StackTrace tip
' in Effective C#

Public Class Whammy

   <Conditional("DEBUG")> _
   Public Shared Sub DebugWrite()
      Debug.WriteLine(GetMyName())
   End Sub

   <Conditional("DEBUG")> _
   Public Shared Sub DebugWriteWithTimeStamp()
      Dim mask As String = String.Format("{0} called at {1}", _
         GetMyName(), DateTime.Now)

      Debug.WriteLine(mask)
   End Sub

   <Conditional("DEBUG")> _
   Public Shared Sub ConsoleWrite()
      Console.WriteLine(GetMyName())
   End Sub

   <Conditional("DEBUG")> _
   Public Shared Sub ConsoleWriteWithTimestamp()
      Dim mask As String = String.Format("{0} called at {1}", _
         GetMyName(), DateTime.Now)
      Console.WriteLine(mask)
   End Sub

   <Conditional("DEBUG")> _
   Public Shared Function GetMyName()
      Dim trace As New StackTrace()
      Try
         Return String.Format("{0}.{1}", _
            trace.GetFrame(2).GetMethod().ReflectedType.FullName, _
            trace.GetFrame(2).GetMethod().Name)
      Catch ex As Exception
         Return String.Format(trace.GetFrame(1).GetMethod().Name)
      End Try
   End Function
End Class

The code is pretty straightforward after you understand how its three key technologies—ConditionalAttribute, Reflection, and StackTrace and StackFrame objects—work. The first statement in bold (Whammy.ConsoleWrite) demonstrates how easy it is to employ the Whammy class. The next statement in bold (ConditionalAttribute("Debug")>) shows the proper usage of the ConditionalAttribute class.

When the class “undefines” DEBUG, the Whammy code is pretty much ignored, which mitigates any costs of using the trace capability after deployment. However, you could use a custom string for this attribute and turn the Whammy back on after deployment if you needed to.

The GetName function in bold demonstrates how you can get a StackTrace and StackFrame. GetMethod returns a MethodInfo object, which tells you about the reflected type, the namespace and class, and the method name. The integer passed to GetFrame tells you which frame you’d like to get: GetFrame(0) gets the method currently in; GetFrame(1) would get any method that called GetMyName; because you want the external caller, you use GetFrame(2).

The output from the sample indicates that the caller was WhammyDemo.Module1.Main, the Main method.

Another .NET Goody

The difference between advanced solutions and a lot of unnecessary work is knowing your tools’ capabilities. Six years after first using the very rich, diverse set of tools in .NET, I am still amazed at how many cool technologies, such as reflection, attributes, and stack information, are available.

Acknowledgements

I’d like to offer a special thanks to Bill Wagner and Addison Wesley for letting me borrow the stack trace technique from Bill’s excellent book Effective C#.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his new book UML DeMystified from McGraw-Hill/Osborne. Paul is an architect for Tri-State Hospital Supply Corporation. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.

Copyright © 2006 by Paul T. Kimmel. All Rights Reserved.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read