DekGenius.com
[ Team LiB ] Previous Section Next Section

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).

    [ Team LiB ] Previous Section Next Section