C# - Obtaining Method Caller Information [Intermediate]


You're hard at work developing a new logger for your project and you have a phone call. It's a call from home, it must be really late. "Oh, I only wish C# methods had a caller ID feature", you think to yourself. You could really use that information in your logger. Well, as of .NET 4.5 / Visual Studio 2012 this is possible! A method can now obtain information about the method that called it.

 

Usage

A method can obtain the following information about its caller:
  • Member name
  • Source file path
  • Line number
To get this information all we need to do is define optional parameters and decorate them with an attribute from the System.Runtime.CompilerServices namespace. The compiler will do the rest.
Let's see an example:
public static void Log(string msg, 
    [CallerMemberName] string memberName  = "", 
    [CallerFilePath] string filePath = "", 
    [CallerLineNumber] int lineNumber = 0)
{
    string msgToLog = string.Format("{0} ({1} line {2}): {3}",
        memberName, filePath, lineNumber, msg);
    Trace.WriteLine(msgToLog);
}
 
The simple Logger class above defines a single method - Log. The method accepts four parameters, the first of which is the message to be logged and the rest will automatically receive caller information.

Now let's use our logger:
class BusinessLogic
{
    public void PerformLogic()
    {
        // do logic
        Logger.Log("Finished performing logic.");
    }
}
 
As you can see, when we invoke the logger we only have to provide a value for the first parameter - the message. The rest of the parameters are automatically provided values by the compiler. The output when the above method executes will be similar to the following:
PerformLogic (c:\Project\Program.cs line 26): Finished performing logic.

To summarize, here is how we obtain caller information:
  • To obtain the member name, define an optional parameter of type string and decorate it with the CallerMemberName attribute
  • To obtain the source file path, define an optional parameter of type string and decorate it with the CallerFilePath attribute
  • To obtain the line number, define an optional parameter of type int and decorate it with the CallerLineNumber attribute
The order of the above parameters is insignificant and we aren't required to define all of them.

 

How Does it Work?

The short answer: with some help from our good ol' friend, the compiler. 

When the compiler encounters a call to a method that has parameters with the above attributes, and values to those parameter are omitted, it steps in and provides the values automatically.

Here is the IL code for the PerformLogic method:
.method public hidebysig instance void  PerformLogic() cil managed
{
  .maxstack  8
  IL_0000:  ldstr      "Finished performing logic."
  IL_0005:  ldstr      "PerformLogic"
  IL_000a:  ldstr      "c:\\Project\\Program.cs"
  IL_000f:  ldc.i4.s   26
  IL_0011:  call       void ConsoleApplication1.Logger::Log(string,
                                                            string,
                                                            string,
                                                            int32)
  IL_0016:  ret
} // end of method BusinessLogic::PerformLogic

As you can easily see, the member name, source file path and line number are specified as literal values. In fact, once the compiler is done, there is no evidence that something special ever happened here. This looks just like another method call with literal values as parameters. Nice...

 

Implementing the INotifyPropertyChanged Interface

Another scenario where obtaining method caller information is very useful is when implementing the INotifyPropertyChanged interface, which is used for data binding. It allows a property to notify that its value has changed. The interface is defined as follows:
public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}
 
The interface defines an event, which needs to be fired when a property changes. Consider the following implementation of INotifyPropertyChanged:
class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string firstName;
    private string lastName;

    public string FirstName
    {
        get { return this.firstName; }
        set
        {
            this.firstName = value;
            NotifyPropertyChanged("FirstName");
        }
    }

    public string LastName
    {
        get { return this.lastName; }
        set
        {
            this.lastName = value;
            NotifyPropertyChanged("LastName");
        }
    }

    private void NotifyPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
 
The person class defines two properties: FirstName and LastName. To support data binding, it implements the INotifyPropertyChanged interface, which requires that it defines the PropertyChanged event. Whenever the value of a property changes, the event is fired and is provided with the name of that property. The setter of the properties above, calls the private NotifyPropertyChanged method, which fires the event.

Now imagine that you decide to rename the LastName property to Surname. Note that when the setter of LastName calls the NotifyPropertyChanged it provides it with the name of the property as a string literal. When renaming the property to Surname, we must also remember to modify the string literal accordingly. Failing to do so may cause the binding to fail. I'm sure you can appreciate how error prone this approach can be... But until recently, it was our only option. Not any more! Consider the following revised code (with only the relevant parts for simplification):
public string LastName
{
    get { return this.lastName; }
    set
    {
        this.lastName = value;
        NotifyPropertyChanged();
    }
}

private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
 
Now the NotifyPropertyChanged uses the CallerMemberName attribute to have the compiler provide the member name (in this case the property name) automatically. See how the call to CallerMemberName inside the setter of the property no longer needs to specify the property name? Now if we rename the LastName property to Surname, data binding is guaranteed to continue working properly. Is that cool or what?

 

An Alternative to Caller Information (and why it isn't as good)

This feature is new to .NET 4.5 / Visual Studio 2012. If you're using a previous version, you need to find an alternative. You can use the System.Diagnostics.StackFrame class to obtain information about the calling method at runtime. Consider this revised Log method:
public static void Log(string msg)
{
    StackFrame stackFrame = new StackFrame(1);
    string methodName = stackFrame.GetMethod().Name;
    string fileName = stackFrame.GetFileName();
    int lineNumber = stackFrame.GetFileLineNumber();

    string msgToLog = string.Format("{0} ({1} line {2}): {3}",
        methodName, fileName, lineNumber, msg);
    System.Diagnostics.Trace.WriteLine(msgToLog);
}
 
Here we instantiate a StackFrame and provide it with a value of 1, to get information about the method one frame up the call stack. We then use it to extract the method name, file name and line number.

This approach uses information that is available at runtime and therefore suffers from the following drawbacks:
  • The file name and the line number are typically retrieved from the debugging symbols, which aren't always available
  • If using an obfuscator, methods may be renamed to meaningless, cryptic names; these obfuscated names will be available through StackFrame rather than the original name
  • When the JIT compiler compiles IL to native code, it may inline simple methods to improve performance, in which case the method one frame up the call stack may be different than the method we expect it to be based on the source code; in fact, using the StackFrame approach to implement INotifyPropertyChanged is almost guaranteed to fail, because properties are such great candidates for inlining, due to their typical simplicity
  • Instantiating and using a StackFrame is extra work that has to be done at runtime and takes processing cycles

Using method caller information has none of the above drawbacks. The information is emitted by the compiler and appears in the IL code as literals. This means that this information is guaranteed to exist, is resilient to obfuscation and method inlining, and is as efficient as accessing a parameter.

 

Final Words

There's one thing that I feel is missing from the implementation of this cool new feature. I wonder why there isn't another attribute to allow access to the fully qualified name of the type that contains the calling method. This could be useful in logging / diagnostics scenarios. But overall this is an excellent new feature, which I hope you will find as useful as I have.

Comments