The call stack is a useful tool during debugging. The .NET Framework includes classes in the System.Diagnostics namespace that provide access to the call stack, enabling you to examine or display this information in trace or logging messages, much like the call stack information available in exception objects, which were discussed in Chapter 2.
The StackTrace class is used to gain access to the call stack and represents the call stack for a specific thread. Each StackTrace object provides access to one or more StackFrame objects, each of which represents one method call in the stack. The relationship between StackTrace and StackFrame objects is shown in Figure 9-2.
As you can see, the StackTrace class serves primarily as a container for StackFrame objects. The following StackTrace property and methods are the most commonly used (refer to the Microsoft Developer Network (MSDN) Library for the other methods of StackTrace):
FrameCount This property holds the number of StackFrame objects associated with this StackTrace instance.
GetFrame This method returns one StackFrame object specified by passing the stack frame index as a parameter, with 0 referring to the most recent frame.
ToString This method returns a string that describes the call stack.
There are two commonly used constructors for the StackTrace class. The default constructor is used to create a StackTrace object for the current thread, without file information, as shown here:
StackTrace callstack = new StackTrace();
File information can be useful for debugging purposes, as it adds both the file name and line numbers to your stack tracing information. To create a StackTrace object for the current thread that includes file information, pass a Boolean true value to a second version of the constructor, as shown here:
StackTrace callstack = new StackTrace(true);
The ToString method from the StackFrame class can be used to generate a string that describes the call stack, using code such as the following:
StackTrace callStack = new StackTrace(); string traceInfo = callStack.ToString();
This code is easy to use, and it provides basic information that can help you determine the sequence of method calls that resulted in the call to the current method. If you need more detailed information, such as line offset information to aid in your tracing efforts, you can take advantage of methods and properties in the StackFrame class. The most commonly used methods in the StackFrame class are shown here:
GetFileLineNumber Returns the line number for the code being executed for this frame, if debug symbols are available
GetFileName Returns the file name containing the code being executed, if debug symbols are available
GetMethod Returns a MethodBase object that describes the method executing this frame
ToString Returns a string that describes the stack frame
In many cases, simply calling the StackFrame.ToString method will provide you with a reasonable string describing the stack frame. The following method uses StackFrame.ToString to display a stack trace with more detailed information than the previous example, including more detailed information for each stack frame:
protected string GetStackTrace() { StringBuilder result = new StringBuilder(); // Create a call stack trace with file information. StackTrace trace = new StackTrace(true); int frameCount = trace.FrameCount; for(int n = 0; n < frameCount; ++n) { StackFrame frame = trace.GetFrame(n); result.Append(frame.ToString()); } return result.ToString(); }
The GetStackTrace method iterates over each StackFrame object in the current stack by calling GetFrame. As each StackFrame object is retrieved, its ToString method is called to assemble a string describing the entire call stack.
Although the StackFrame.ToString method returns a reasonable string that describes the stack frame, you might want to exercise more control over the information displayed for the trace. Instead of relying on a call to the ToString method from each stack frame, you can access properties and methods to build up a string manually, as you’ll see in the next section.
The source file name and offset information provided by a StackFrame object includes file names and line numbers instead of method names or parameter data. Although this is enough information to look up the method names manually, a stack trace is much easier to use if it includes more detailed information about the methods that are invoked for each frame in the call stack.
The StackFrame.GetMethod method returns a MethodBase object that represents the method that was executing code in the stack frame. By using reflection (discussed in Chapter 6), you can obtain information about the method, including the method name and any parameters associated with it. The following method uses the MethodBase class to provide detailed information about the call stack:
protected string GetStackTraceWithMethods() { StringBuilder result = new StringBuilder(); StackTrace trace = new StackTrace(true); int frameCount = trace.FrameCount; for(int n = 0; n < frameCount; ++n) { StringBuilder frameString = new StringBuilder(); StackFrame frame = trace.GetFrame(n); int lineNumber = frame.GetFileLineNumber(); string fileName = frame.GetFileName(); MethodBase methodBase = frame.GetMethod(); string methodName = methodBase.Name; ParameterInfo [] paramInfos = methodBase.GetParameters(); result.AppendFormat("{0} - line {1}, {2}", fileName, lineNumber, methodName); if(paramInfos.Length == 0) { // No parameters for this method; display // empty parentheses. result.Append("()\n"); } else { // Iterate over parameters, displaying each parameter's // type and name. result.Append("("); int count = paramInfos.Length; for(int i = 0; i < count; ++i) { Type paramType = paramInfos[i].ParameterType; result.AppendFormat("{0} {1}", paramType.ToString(), paramInfos[i].Name); if(i < count - 1) result.Append(","); } result.Append(")\n"); } } return result.ToString(); }
The GetStackTraceWithMethods method uses the reflection namespace to gather more information about the call stack and presents it in a specialized format. Each method in the call stack is displayed, along with source file line numbers and parameter information. This is achieved by retrieving a MethodBase object for each StackFrame object that exists in the call stack. Parameter information is retrieved by calling the GetParameters method, which returns an array of ParameterInfo. Each parameter in the array is inspected and then added to the result string.