Tuesday, February 27, 2007 5:38 PM
One of my absolute favorite things to do is to work on framework components. I'm not sure why really, I don't think it has anything to do with their importance, I think it's more to do with the way they're used. They're built by programmers for programmers, there's usually not a UI involved so interaction is handled 'code-to-code', and error conditions are normally communicated via exceptions. There's just something very cool to me about having to write something that has to be as absolutely rock solid as possible while making it as absolutely flexible as possible since I really won't know in exactly what context it will be used.
So. as geeky as this sounds, I've pretty much been in second heaven lately since I've been refactoring some of our framework components. The one I've been working on today is logging. We decided that we needed the current method name reflected in the log statement so we can trace excecution a bit easier. Yes, this will come at a slight performance impact but we only have logging enabled on production systems for the most extreme levels of errors so we shouldn't have to worry about it too often, and when we do need it we really need it so it's OK.
The easiest way to get the name of your calling method is via reflection using MethodBase, like so...
MethodBase currentMethod = MethodBase.GetCurrentMethod();
string caller = currentMethod .DeclaringType.FullName + '.' + currentMethod .Name;
This works great...if you're currenlty inside of the method you're interested in. The obvious problem here when it comes to logging is the GetCurrentMethod() call will actually return our logging routine since that's where it's being called from. What we need is for the logging routine to know who it was that called it. The obvious solution here is to simpy pass a reference to the method into the logging routine, similar to how we can verify a calling object. When I'm building frameworks, however, I like for the API to be as simple as possible to use. This means that anything that we can figure out for ourselves we don't need the caller to tell us. We want our API, especially something as utilitarian as logging, to be as simple to use as possible. If people have to really think about how to use our logging, then they won't use our logging and we won't be able to learn anything when something goes wrong. It's for this same reason that I'm also a big fan of supplyng your team code snippets for common tasks, we want to make things as absolutely clean and simple for our users as possible. If we don't then someone else will.
So, ease of use pontifications aside, what's the easiest way that we can figure out who called us with absolutely as little participation from our users as possible? Think about you're first computer science class, how do most modern runtimes keep track of where they are? The stack. How do we find where something went wrong, when (gasp!) it does? The stack trace. Luckily, .NET has functionality built right into the framework to get our current stack trace and move easily to any frame within it...
// Jump up the stack frame one level and locate the
// calling method.
StackFrame stackFrame = new StackFrame(1);
MethodBase callingMethod = stackFrame.GetMethod();
// Build a string containing the namespace and method name
string caller = callingMethod.DeclaringType.FullName +
'.' + callingMethod.Name;
And that's all there is to it. We simply create an instance of the StackFrame class telling it how far up the stack we want to look. In this case, we're looking 1 level up, which means we're looking at the method who called us. Note that we can go as far up as we need, as long as we don't go deeper than the current stack. Once we get the StackFrame, we can get the method (every frame has exactly one method) that was in that frame. Now that we have the method, it's exacty like we called GetCurrentMethod() within it, we can get the namespace (DeclaringType.FullName) and the method name easily. What's more is that this technique can't be spoofed in the way that the verifying calling objects technique can.
If you've never looked in the System.Diagnostics namespace there are some very cool things in there. Go ahead, take a look now...I'll wait :) We can do some pretty advanced things with debugging, programmtically affect our configurations, we can even create an instance of the StackTrace object if we want to work with a whole series of StackFrame objects. You can do some very cool stuff with this and just a little imagination but keep in mind that we are playing with reflection so you might see a slight performance impact while you're 'imagining'. That impact, however, shouldn't stop you from simply exploring.