Recipe 9.5 A More Flexible StackTrace Class
Problem
You have a
StackTrace class containing a listing of stack
frames. You need to iterate through these stack frames as if you were
using an Array type object.
Solution
Use the adapter design
pattern to adapt the public interface of a
StackTrace object to look like a
Collection type object. The
StackTraceArray class implements this design
pattern:
using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;
public class StackTraceArray : StackTrace, IList
{
public StackTraceArray( ) : base( )
{
InitInternalFrameArray( );
}
public StackTraceArray(bool needFileInfo) : base(needFileInfo)
{
InitInternalFrameArray( );
}
public StackTraceArray(Exception e) : base(e)
{
InitInternalFrameArray( );
}
public StackTraceArray(int skipFrames) : base(skipFrames)
{
InitInternalFrameArray( );
}
public StackTraceArray(StackFrame frame) : base(frame)
{
InitInternalFrameArray( );
}
public StackTraceArray(Exception e, bool needFileInfo) : base(e, needFileInfo)
{
InitInternalFrameArray( );
}
public StackTraceArray(Exception e, int skipFrames) : base(e, skipFrames)
{
InitInternalFrameArray( );
}
public StackTraceArray(int skipFrames, bool needFileInfo) :
base(skipFrames, needFileInfo)
{
InitInternalFrameArray( );
}
public StackTraceArray(Thread targetThread, bool needFileInfo) :
base(targetThread, needFileInfo)
{
InitInternalFrameArray( );
}
public StackTraceArray(Exception e, int skipFrames, bool needFileInfo) :
base(e, skipFrames, needFileInfo)
{
InitInternalFrameArray( );
}
private StackFrame[] internalFrameArray = null;
private void InitInternalFrameArray( )
{
internalFrameArray = new StackFrame[this.FrameCount];
for (int counter = 0; counter < base.FrameCount; counter++)
{
internalFrameArray[counter] = base.GetFrame(counter);
}
}
public string GetFrameAsString(int index)
{
StringBuilder str = new StringBuilder("\tat ");
str.Append(GetFrame(index).GetMethod( ).DeclaringType.FullName);
str.Append(".");
str.Append(GetFrame(index).GetMethod( ).Name);
str.Append("(");
foreach (ParameterInfo PI in GetFrame(index).GetMethod( ).GetParameters( ))
{
str.Append(PI.ParameterType.Name);
if (PI.Position < (GetFrame(index).GetMethod( ).GetParameters( ).Length - 1))
{
str.Append(", ");
}
}
str.Append(")");
return (str.ToString( ));
}
// IList properties/methods
public bool IsFixedSize
{
get {return (internalFrameArray.IsFixedSize);}
}
public bool IsReadOnly
{
get {return (true);}
}
// Note that this indexer must return an object to comply
// with the IList interface for this indexer
public object this[int index]
{
get {return (internalFrameArray[index]);}
set {throw (new NotSupportedException(
"The set indexer method is not supported on this object."));}
}
public int Add(object value)
{
return (((IList)internalFrameArray).Add(value));
}
public void Insert(int index, object value)
{
((IList)internalFrameArray).Insert(index, value);
}
public void Remove(object value)
{
((IList)internalFrameArray).Remove(value);
}
public void RemoveAt(int index)
{
((IList)internalFrameArray).RemoveAt(index);
}
public void Clear( )
{
// Throw an exception here to prevent the loss of data
throw (new NotSupportedException(
"The Clear method is not supported on this object."));
}
public bool Contains(object value)
{
return (((IList)internalFrameArray).Contains(value));
}
public int IndexOf(object value)
{
return (((IList)internalFrameArray).IndexOf(value));
}
// IEnumerable method
public IEnumerator GetEnumerator( )
{
return (internalFrameArray.GetEnumerator( ));
}
// ICollection properties/methods
public int Count
{
get {return (internalFrameArray.Length);}
}
public bool IsSynchronized
{
get {return (internalFrameArray.IsSynchronized);}
}
public object SyncRoot
{
get {return (internalFrameArray.SyncRoot);}
}
public void CopyTo(Array array, int index)
{
internalFrameArray.CopyTo(array, index);
}
}
Discussion
The adapter design
pattern allows an existing object to be tailored to
operate like a different object. Basically, a new class is created to
act the same as the original class. This new object exposes an
interface of the desired type, and the exposed members adapt and
forward their calls to the underlying original class.
This can be done using several techniques. One technique involves
using containment. A new class is created that contains a reference
to the original class. This new class acts as an intermediary class
and forwards calls to the contained original class. A second
technique, which is used in this recipe, is to use inheritance to
create a totally new class, which then exposes a different interface
used to forward calls to the base class members.
This recipe adapts the
System.Diagnostics.StackTrace class to look and
act like a collection of stack frames. The
StackTrace class provides a convenient way to
obtain a stack trace from the current point in code, an exception
object, or a specific thread. Unfortunately, the
StackTrace provides only a very simplified way to
get at each stack frame. It would be much better if the
StackTrace object operated like an array. To make
this happen, an intermediate object called
StackTraceArray is created that inherits from
StackTrace and implements the
ICloneable, IList,
ICollection, and IEnumerable
interfaces—the same interfaces that the
Array class implements.
The constructors for the StackTraceArray class
mimic the StackTrace constructors. Each
StackTraceArray constructor passes its work along
to the base class using the base keyword:
public StackTraceArray( ) : base( )
Each StackTraceArray constructor contains a call
to the private method InitInternalFrameArray. This
private method copies all of the individual
StackFrame objects from the base
StackTrace object into a private field of type
StackFrame[] called
internalFrameArray. The
StackTraceArray uses the
internalFrameArray field as a convenient storage
mechanism for each individual StackFrame object;
in addition, we get a free implementation of the
IEnumerator interface. It also makes it easier to
make the StackTraceArray class look and feel more
like an array as opposed to a StackTrace object.
Another useful method added to the StackTraceArray
class is the public GetFrameAsString method. This
method accepts an index of a specific StackFrame
object in the internalFrameArray field. From this
StackFrame object, it constructs a string similar
to the string output for each StackFrame.
The methods implemented from the IList,
ICollection, and IEnumerable
interfaces forward their calls on to the
internalFrameArray field, which implements the
same interfaces—throwing the
NotSupportedException for most of these interface methods.
The StackTrace object can now be used as if it
were an array, through the intermediate
StackTraceArray object. To obtain a
StackTraceArray object for the current point in
code, use the following code:
StackTraceArray arrStackTrace = new StackTraceArray( );
To display a portion or all of the stack trace, use the following
code:
// Display the first stack frame
Console.WriteLine(arrStackTrace[0].ToString( ));
// Display all stack frames
foreach (StackFrame SF in arrStackTrace)
{
Console.WriteLine("stackframe: " + SF.ToString( ));
}
To obtain a StackTraceArray object from a thrown
exception, use the following code:
...
catch (Exception e)
{
StackTraceArray EST = new StackTraceArray(e, true);
Console.WriteLine("TOSTRING: " + Environment.NewLine + EST.ToString( ));
foreach (StackFrame SF in EST)
{
Console.WriteLine(SF.ToString( ));
}
}
To copy the StackFrame objects to a new array, use
the following code:
StackFrame[] myNewArray = new StackFrame[arrStackTrace.Count];
arrStackTrace.CopyTo(myNewArray, 0);
You will notice that the first StackFrame object
in the stack trace contains something like the following:
at AdapterPattern.StackTraceArray..ctor( )
This is actually the constructor call to our
StackTraceArray object. This information is
usually not necessary to display and can be removed quite easily.
When creating the StackTraceArray object, pass in
an integer one as an argument to the constructor. This will force the
first stack frame (the one containing the call to the
StackTraceArray constructor) to be discarded:
StackTraceArray arrStackTrace = new StackTraceArray(1);
You should note that the Add,
Insert, Remove, and
RemoveAt methods on the IList
interface of an Array type throw the
NotSupportedException because an array is fixed in length,
and these methods will alter the length of the array.
See Also
See the "StackTrace Class" and
"IList Interface" topics in the
MSDN documentation.
Also
see the "Adapter Design Pattern"
chapter in Design Patterns by Erich Gamma et al.
(Addison Wesley).
|