Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Reduce debugging time using the .NET StackTrace class

4.64/5 (10 votes)
18 Nov 2011CPOL5 min read 51.5K   753  
Use the .NET StackTrace class to position yourself in the right place of source code that threw an Exception.

StackTraceDumperForm in action

Abstract

The article describes how to use the .NET StackTrace class and its frames to navigate through the source code during Exception handling. The class used to extract info from the StackTrace is hosted inside a DLL assembly and ready to use inside any .NET 3.5 or higher Solution.

Introduction

First of all, what is a stack trace? During the execution of a program, it's usual to have functions or methods which call other functions and/or methods; these, in turn, call other functions and/or methods so that we have a chain of calls. StackTrace is a structure reporting this sequence of calls in the right order they occurred.

As stated by the MSDN: "Stack trace information will be most informative with Debug build configurations. By default, Debug builds include debug symbols, while Release builds do not. The debug symbols contain most of the file, method name, line number, and column information used in constructing StackFrame and StackTrace objects."

Now the question is how to use these information? Very often, during debugging or in short cycle development, I struggle with Exceptions threw by very deep code; in cases like that, what I can do is: navigate from call to call diving into the code until the source of the thrown exception is reached... another possibility is read the call stack and see the line of code that threw the exception. The class StackTrace lists the sequence of function/method calls, so the main idea is use this list of calls to make some sort of automation for navigating through the source code.

Using the code

The core is the .NET StackTrace class. If you create an instance of the class in any point of your code (see the snippet of code below), you can inspect the chain of calls that bring you to that point of your code!

C#
using System.Diagnostics;
. . .
StackTrace stacktrace = new StackTrace();
StackFrame[] frames = stacktrace.GetFrames();
string stackName = string.Empty;
foreach (StackFrame sf in frames){
    System.Reflection.MethodBase method = sf.GetMethod();
    stackName += (method.Name+"|");
}//end_foreach

StackTrace in action

From the picture, you can see the chain: The Main method called _nExecuteAssembly, which called RunUsersAssembly, and so on.

This kind of information becomes really useful when debugging because when your code or the framework code throws an Exception, the Exception contains lots of information that you can use to navigate inside the source code, formally building some sort of automation facility. This is what is done by the class StackTraceDumperForm. To let the StackTraceDumperForm help you during your debugging session, you must collaborate doing three things:

  1. don't destroy the stack trace in your code (which is recommended)
  2. inside the catch part of a try-catch, use the Exception to build a StackTrace instance and pass it to the StackTraceDumperForm ctor
  3. let run only one instance of Visual Studio, so that StackTraceDumperForm shall be able to hook it, find, and highlight the line causing the Exception

Let's see the steps one by one.

  1. Don't destroy the stack trace
  2. This is good:

    C#
    try {
        . . .
    }//end_try
    catch( Exception exx ) {
        Debug.WriteLine( exx.Message );
        if( null != exx.InnerException )
             Debug.WriteLine( exx.InnerException.Message );
    
        throw;
    }//end_catch

    Because every Exception which is been thrown is caught, is inspected to show some useful information, and is re-thrown without destroying the stack of calling functions.

    On the other hand, this is bad:

    C#
    try {
        . . .
    }//end_try
    catch( Exception exx ) {
        //- NEVER DO THAT:
        //- throws a new exception using a piece of information contained 
        //- inside the original exception and resets the stack trace
        throw new Exception( "The exception message is" + exx.Message );
    }//end_catch

    Sure, you display the same message of the original exception but every time you create a new Exception, the stack of calling functions is reset and begins from the point where the Exception is created! For the same reason, this is also bad:

    C#
    try {
        . . .
    }//end_try
    catch( Exception exx ) {
         //- NEVER DO THAT:
         //- throws a new exception using the information contained 
         //- inside the original exception and resets the stack trace
         throw new Exception("The exception is", exx );
    }//end_catch

    And, actually, this is also a bad thing to do:

    C#
    try {
        . . .
    }//end_try
    catch( Exception exx ) {
         //- NEVER DO THAT:
         //- throws the original exception but resets the stack trace
         throw exx;
    }//end_catch

    You can download the demo project and inspect the cases, you can also use StackTraceDumperForm to visualize how the stack is cut:

    StackTraceDumperForm with stacktrace preserved

    You can see how the stack trace displays the guilty function (the last one: DdoItTooEarly method) because the stack is preserved by our code!

    StackTraceDumperForm with stacktrace destroyed

    The guilty function is CallAFailureFuncton1() because the exception is created inside the catch block of this function.

    StackTraceDumperForm with stacktrace destroyed

    Again, the guilty function is CallAFailureFuncton2() because the exception is created inside the catch block of this function.

    StackTraceDumperForm with stacktrace destroyed

    Finally the guilty function is CallAFailureFuncton3() because even though we re-throw the original exception, writing "throw exx;" instead of "throw;" really destroys the stack of calling functions!

  3. Inside the catch part of a try-catch, use the Exception to build a StackTrace instance and pass it to the StackTraceDumperForm ctor
  4. The StackTraceDumperForm needs an instance of System.Diagnostics.StackTrace to work, not an Exception. The instance of the Exception class contains a StackTrace; so you can extract the StackTrace from the exception and pass it to the ctor! The snippet of code that I usually use is:

    C#
    try {
        . . .
    }//end_try
    catch( Exception exx ) {
        Debug.WriteLine( exx.Message );
        if( null != exx.InnerException )
            Debug.WriteLine( exx.InnerException.Message );
    #if DEBUG
        Dictionary<string, string> debugInfos = new Dictionary<string, string>();
        debugInfos.Add( "Method Name", "MyMethodName" );
        var trace = new System.Diagnostics.StackTrace( ex, true );// true means get line numbers.
        var frm = new StackTraceDumperForm.StackTraceDumperForm( debugInfos, ex.Message, trace );
        frm.ShowDialog();
    #endif
    }//end_catch

    The DEBUG macro just avoid that inside production code, StackTraceDumperForm stops the code execution!

  5. Let run only one instance of Visual Studio
  6. some automation of StackTraceDumperForm

    Do you see the Visual Studio 2010 logo inside the StackTraceDumperForm? On the left side, there is the string: "Go to line..." This information is extracted from the StackTrace object and if you press the button (green arrow), you will be set at the line displayed inside the class displayed, but only if there is one and only one instance of Visual Studio, because StackTraceDumperForm is not able to distinguish which instance of IDE has the class you are interested in! Therefore, if you want the automation working, remember to have only one running instance of Visual Studio! Another thing: the green information is about your code, the code that StackTraceDumperForm is able to reach; while in gray is the information about code too deep to be reached, i.e., inside the core class of the Framework! The last thing: another little automation facility is pointed by the blue arrow; if you press the button, then the Output window of the Visual Studio will be cleared!

Conclusion

I'm used to use this simple utility since 2007 and find it very useful during debugging sessions; so I decided to share it with all of you. As usual, feel free to use the code and to modify it as you like (and let me know your impressions)!

History

  • 19 November, 2010 - First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)