Monday, February 05, 2007

Stack Reflections

If you haven't noticed by my choice of presentation topics, I'm a big fan of Reflection. I like the flexibility and scalability it allows me to build into my applications. There are also some neat little tricks you can use with it, in conjunction with the stack trace, to better track your errors and debugging statements.

First of all, how would you like to be able to write a trace statement that shows your methods called without having to remember to code the method names? This can be accomplished by "walking the stack trace" during a method call and using the reflected method information from the stack to determine what method called your function. It works something like this:

I'm going to write a method called TraceMethodStart with no parameters. This method will load up the stack trace, walk the stack frame back one level, and then write out to Trace the method name and module name of the method that called the TraceMethodStart function.

public void TraceMethodStart()
{
System.Diagnostics.StackTrace currentStack = new System.Diagnostics.StackTrace();
string sMethod = "Unknown method";
string sClass = "Unknown type";

//make sure I have frames
if (currentStack.FrameCount > 0)
{
//get the method of the frame one up from me

System.Reflection.MethodBase oBase = currentStack.GetFrame(1).GetMethod();
sMethod = oBase.Name;
sClass = oBase.DeclaringType.ToString();
}

if (System.Web.HttpContext.Current != null)
System.Web.HttpContext.Current.Trace.Write("Starting: " + sClass + " " + sMethod);


}

Now in my GetRob() function I do this:

namespace Sltc
{
public class Rob
{
public void GetRob()
{
TraceMethodStart();
}
}
}

And a "Starting: Sltc.Rob GetRob" will appear in our trace statements.

Now that's useful. But it would be even more useful if we knew what was being passed to the function for parameters. Using the params keyword we can extend this function to accept a variable list of arguments so that we can call it from anywhere in our code and get passed any number of arguments. This will allow us to include the variables the function was given when it was called.


public void TraceMethodStart(params object[] oParms)
{
System.Diagnostics.StackTrace currentStack = new System.Diagnostics.StackTrace();
string sMethod = "Unknown method";
string sClass = "Unknown type";

//make sure I have frames
if (currentStack.FrameCount > 0)
{
//get the method of the frame one up from me

System.Reflection.MethodBase oBase = currentStack.GetFrame(1).GetMethod();
sMethod = oBase.Name;
sClass = oBase.DeclaringType.ToString();
}

if (System.Web.HttpContext.Current != null)
System.Web.HttpContext.Current.Trace.Write("Starting: " + sClass + " " + sMethod);


foreach(object obj in oParms)
{
if (System.Web.HttpContext.Current != null)
System.Web.HttpContext.Current.Trace.Write("Parm: " + oParm.ToString());
}
}

Now if you are in a method like so, you can do this:

function MyMethod(string s, int i)
{
TraceMethodStart(s, i);
}

Another handy thing you can do with this is to grab the cause for exceptions. A lot of times the stack trace is huge and it's difficult to track down or even remember to write code to track where your exceptions are occuring. However, if you were to create your own exception, you could use a similar code sequence to grab the cause of the trouble:

public class SLTCException : System.Exception
{

private string _sClass = "Unknown";
private string _sMethod = "Unknown";

public SLTCException() : base()
{
System.Diagnostics.StackTrace currentStack = new System.Diagnostics.StackTrace();
if (currentStack.FrameCount > 0)
{
System.Reflection.MethodBase oBase = currentStack.GetFrame(1).GetMethod();
_sMethod = oBase.Name;
_sClass = oBase.DeclaringType.ToString();
}
}
}

Happy stacking!

Rob