If you've been around C# long enough, you've probably had a need at one time or another to figure out some information about code that's being executed. It might be a method name, the file being executed, or even more specific details like the line number.
These things can be accessed through Reflection, as you might expect, but that might not be the best way to go. This post will discuss some of the available language features in C# to help you avoid resorting to it if you don't have to.
Why Not Reflection?
Reflection is quite a bit like using Entity Framework; both technologies are quite powerful, but there are tons of potentially nasty things going on behind the scenes that you probably don't want happening in the first place.
Performance can be a major concern when Reflection is involved as it requires huge amounts of metadata about the current code to be loaded and processed. According to this post by fellow Microsoft MVP Rick Strahl, it could result in roughly 4x slower processing times (in some cases).
Now, performance numbers should always be taken with a grain of salt. If you are talking processing times that are running in a matter of milliseconds, then a 4x increase might be trivial, likewise if you are talking minutes, then it's a different story.
The gist is this: if Reflection can provide you the solution you need, without compromising performance, then use it - assuming some of the later sections of the post don't do it better.
Calling All Attributes
Let's say you wanted to get the name of a method (it could be a class, property, etc.) for logging purposes. For demonstration purposes, let's just say it looks like this:
public void LogMethodName(string methodName)
Now in the past, if we wanted to retrieve the name of the caller method, we might have to resort to something like this:
public void SomethingHappened()
var method = new StackTrace().GetFrame(0).GetMethod().Name;
public string SomethingHappened()
var method = MethodBase.GetCurrentMethod().Name;
I know, they are gross. Let's turn to some of the Caller Info attributes that were introduced within .NET 4.5, namely
[CallerMemberAttribute]. Unlike the other options, this will simply make a minor change to our
Log() method, which wouldn't require us to pass in a method name (or go through the work of resolving it at all):
public void LogMethodName([CallerMemberName] string methodName = null)
This attribute leverages the fact that the compiler actually knows what everything is at compile-time; as such it will emit specific values as literals into the Intermediate Language (IL).
- It's easy to implement - If you have your code designed in such a way that you can take advantage of the attribute, it doesn't require massive blocks of code in all your methods prior to calling the one you care about.
- It's flexible and easily overridden - Since the
CallerMemberName (and other caller info attributes) require an optional parameter, you can always pass one in to hide the caller information.
- It's WAY faster - Since the caller information is available at compile-time: it's already there. You don't have to process any metadata or dive through the frames of a
A quick example comparing these three approaches yielded the following results (over 1 million iterations):
- Reflection - 1668ms
- StackTrace - 7667ms
- CallerMemberName - 1ms
As you can see,
CallerMemberName is orders of magnitude faster than either of the other two options, in addition to being far more efficient memory-wise.
You might ask, how in the world is this so much faster? Well, let's look behind the scenes at what the generated IL looks like and we'll see why.
Consider the following program:
static void Main(string args)
private static void SomeMethod()
private static void LogSomething([CallerMemberName] string method = null)
If we take a look at the IL generated by this, we'll get our performance answer:
As you can easily see, it's fast because we don't have to look up anything. The
string itself is present directly in the IL itself as opposed to making a long chain of Reflection calls or unraveling a cumbersome Stack Trace.
More Than a
These compile-time attributes aren't limited to only method and property names. You can also access other caller information such as file names and line numbers (both of which are resolved at compile-time) through the following attributes:
[CallerMemberName] - Sets the parameter to the name of the method or property making the call
[CallerFilePath] - Sets the parameter to the full file path (at compile-time) of the call prior
[CallerLineNumber] - Sets the parameter to the line number of the source file (at compile time) making the call
As you can imagine, these attributes could easily be used to create a robust, performant logging system that didn't require anything to be passed to the log method itself:
public void LogToDatabase([CallerFilePath] string callingFilePath,[CallerMemberName]
string callingMember = null, [CallingLineNumber] int? = null)
While these attributes might not always be useful, they could be an excellent option if you are working with a legacy codebase that might be using Reflection or StackTraces to resolve some of this information.
I'd be interested to see if anyone was venturing outside the box and using these attributes to do any wild stuff. If you think you are - feel free to leave a comment.
An experienced Software Developer and Graphic Designer with an extensive knowledge of object-oriented programming, software architecture, design methodologies and database design principles. Specializing in Microsoft Technologies and focused on leveraging a strong technical background and a creative skill-set to create meaningful and successful applications.
Well versed in all aspects of the software development life-cycle and passionate about embracing emerging development technologies and standards, building intuitive interfaces and providing clean, maintainable solutions for even the most complex of problems.