Recipe5.5.Writing a More Flexible StackTrace Class


Recipe 5.5. Writing a More Flexible StackTrace Class

Problem

You have a StackTrace object that contains a listing of stack frames. You need to iterate through these stack frames as if you were using a Collection-type object.

Solution

Wrap the public interface of a StackTrace object to look like a Collection-type object. The StackTraceList class shown in Example 5-1 implements this design pattern.

Example 5-1. Writing a More Flexible StackTrace Class

 using System; using System.Collections; using System.Diagnostics; using System.Reflection; using System.Text; using System.Threading; public class StackTraceList : StackTrace, IList {     public StackTraceList( ) : base( )     {         InitInternalFrameArray( );     }     public StackTraceList(bool needFileInfo) : base(needFileInfo)     {         InitInternalFrameArray( );     }     public StackTraceList(Exception e) : base(e)     {         InitInternalFrameArray( );     }     public StackTraceList(int skipFrames) : base(skipFrames)     {         InitInternalFrameArray( );     }     public StackTraceList(StackFrame frame) : base(frame)     {         InitInternalFrameArray( );     }     public StackTraceList(Exception e, bool needFileInfo) : base(e, needFileInfo)     {         InitInternalFrameArray( );     }     public StackTraceList(Exception e, int skipFrames) : base(e, skipFrames)     {         InitInternalFrameArray( );     }     public StackTraceList(int skipFrames, bool needFileInfo) :         base(skipFrames, needFileInfo)     {         InitInternalFrameArray( );     }     public StackTraceList(Thread targetThread, bool needFileInfo) :         base(targetThread, needFileInfo)     {         InitInternalFrameArray( );     }     public StackTraceList(Exception e, int skipFrames, bool needFileInfo) :             base(e, skipFrames, needFileInfo)     {         InitInternalFrameArray( );     }     private StackFrame[] internalFrameArray = null;     private void InitInternalFrameArray( )     {         internalFrameArray = new StackFrame[base.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

This recipe uses the System.Diagnostics.StackTrace object to obtain a list of stack frames, which it then provides to the user. The StackTrace class provides a convenient way to obtain a stack trace, an exception object, or a specific thread from the current point in code. 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 a collection. To make this happen, you can create an intermediate object called StackTraceList that inherits from StackTrace and implements the ICloneable, IList, ICollection, and IEnumerable interfaces.

The constructors for the StackTraceList class mimic the StackTrace constructors. Each StackTraceList constructor passes its work along to the base class using the base keyword:

 public StackTraceList( ) : base( ) 

Each StackTraceList constructor contains a call to the private method, Init-InternalFrameArray. 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 StackTraceList uses the internalFrameArray field as a convenient storage mechanism for each individual StackFrame object; in addition, you get a free implementation of the IEnumerator interface. It also makes it easier to make the StackTraceList class look and feel more like an array than a StackTrace object.

Another useful method added to the StackTraceList 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 interfacesthrowing the NotSupportedException for most of these interface methods.

The StackTrace object can now be used as if it were a collection, through the intermediate StackTraceList object. To obtain a StackTraceList object for the current point in code, use the following code:

 StackTraceList arrStackTrace = new StackTraceList( ); 

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 StackTraceList object from a thrown exception, use the following code:

 … catch (Exception e) {     StackTraceList EST = new StackTraceList(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.StackTraceList..ctor( ) 

This is actually the constructor call to the StackTraceList object. This information is usually not necessary to display and can be removed quite easily. When creating the StackTraceList 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 StackTraceList constructor) to be discarded:

 StackTraceList arrStackTrace = new StackTraceList(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 (Addison-Wesley).



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net