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.
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"
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.
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.
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.
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#.
No comments:
Post a Comment