Freigeben über


Caller Details

Editor's note: The following blog post was written by Visual C# MVP Dustin Davis

There is no lack of new features in Visual Studio 2012. But even with all the bells, whistles and menu titles that yell at me, I wanted to look for meat closer to the bone. What I found were some neat additions to help with tracing and debugging.

With Visual Studio 2012 we get three new attributes from the System.Runtime.CompilerServices  namespace, CallerMemberNameAttribute, CallerFilePathAttribute and CallerLineNumberAttribute. Using these attributes we can collect certain information about the code execution without having to do any additional work. As much as I love using reflection, these attributes are a welcome addition to my tool belt.

How do we use them?

Their use is simple. They’re applied to optional method parameters. That’s it. At compile time the compiler takes over and automatically resolves the correct value and passes it at the point of invocation.

 

void Method(

    [CallerMemberName] string callerName = ""

    ,[CallerLineNumber] int lineNumber = -1

    ,[CallerFilePathAttribute] string filePath = ""

)

Getting trace data

Trace data. We all need it. How we get it is the issue. When logging an execution trace we usually need caller details, parent types, line numbers, etc. For most of us reflection is the go to solution. It’s much more dynamic and flexible than hard coding values and is easier to maintain in the long run. 

You’ve probably seen or written a trace method like

void Trace(string message, string methodName)

{

    Trace.Write(string.Format("{0}: {1}", methodName, message));

}

And then invoked it using a hard coded value for the method name or using reflection

Trace("Executing", "MyMethod");

Trace("Executing", System.Reflection.MethodBase.GetCurrentMethod().Name);

Or maybe you’ve written some more common like the following

void Trace(string message)

{

    StackTrace stackTrace = new StackTrace();

    StackFrame lastFrame = stackTrace.GetFrame(1);

    string methodName = lastFrame.GetMethod().Name;

 

    Trace.Write(string.Format("{0}: {1}", methodName, message));

}

which uses reflection via StackTrace to get the details of the caller. But with the new attributes, we have another option of writing that same method

void Trace(string message, [CallerMemberName] string methodName = "")

{

    Trace.Write(string.Format("{0}: {1}", methodName, message));

}

We can invoke this version with just our trace message.

Trace("Executing");

 

At compile time the compiler will auto populate the methodName parameter with a value equal to the calling method’s name. If you look at the IL you can see exactly what it’s doing.

.method private hidebysig 

    instance void MyMethod () cil managed 

{

    // Method begins at RVA 0x206f

    // Code size 17 (0x11)

     .maxstack 8

 

    IL_0000: ldarg.0

    IL_0001: ldstr "Executing"

    IL_0006: ldstr "MyMethod"

    IL_000b: call instance void ConsoleApplication1.Program::Trace(string, string)

    IL_0010: ret

} // end of method Program::MyMethod

The compiler is loading the string “MyMethod” as a parameter for the call to the Trace method.

Do we really need another solution?

If we have a solution, and it’s been working for us for so long why the need for something else? Good question! Reflection is not known for performance. To get something as simple as the calling method’s name or line number, reflection is the proverbial sledge hammer.

There is also another issue that you might not think about initially until it bites you. When the assembly goes through obfuscation, the method names will become a cryptic mess of characters. Suddenly, “MyMethod” becomes “xEdjf3d7ldk” so when you go to look at your trace log you’ll see,

xEdjf3d7ldk: Executing

il2dur8dcpw: Executing

kf1ur8ldk83: Executing

Mapping this method name back to the original method would require some work.

The compiler will handle the attributes at compile time and obfuscation doesn’t occur until after the assembly is compiled. This means the information you get from the attributes will match what you have in your source code.

Using attributes where possible cleans up the code and makes it easier to read. When developers look at it, they won’t have to decrypt any reflection code to figure it out its intended purpose is.

Additionally, the attributes work the same in Release mode as they do in Debug mode and don’t need PDBs.

Not quite there yet

While these new attributes are handy, the information they provide is limited. We only get the three attributes for caller name, line number and file path. Since most of us would not add a trace method to every class we build, just having the method name isn’t always going to be enough detail to be useful, but we don’t get access to the containing type, method parameters or any of the other information that we could easily get using reflection. But this is a good first step.

Other uses

Are there other uses for these attributes beyond tracing and debugging? There are! My favorite use is when implementing the INotifyPropertyChanged interface which requires magic strings for passing the property names.

If you’ve seen my blog or been to my code camp sessions, you may have seen that I have many alternatives for implementing INotifyPropertyChanged including using aspects (Aspect Oriented Programming), and a T4 template (code generation). Anything that makes implementing this interface easier is awesome in my book.

public class DataModel : INotifyPropertyChanged

{

    private int _myProperty;

    public int MyProperty

    {

        get { return _myProperty; }

        set { _myProperty = value; OnPropertyChanged("MyProperty"); }

    }

 

    public event PropertyChangedEventHandler PropertyChanged;

 

    protected void OnPropertyChanged(string propertyName)

    {

        if (PropertyChanged != null)

        {

            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));

        }

    }

}

Not only do we have to write all of that boiler plate code in the property setter, but we have to maintain it too! I don’t think I need to tell you about the problems with magic strings. This is also prone to error with obfuscation (yes, I know you wouldn’t obfuscate a model you’re binding to).

With a quick change, we can avoid having to deal with magic strings.

public class DataModel : INotifyPropertyChanged

{

    private int _myProperty;

    public int MyProperty

    {

        get { return _myProperty; }

        set { _myProperty = value; OnPropertyChanged(); }

    }

 

    public event PropertyChangedEventHandler PropertyChanged;

 

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")

    {

        if (PropertyChanged != null)

        {

            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));

        }

    }

}

The CallerMemberNameAttribute works with not only methods, but properties and events too. This lets us update the INotifyPropertyChanged implementation to make use of the CallerMemberNameAttribute to provide the name of the changed property to the OnPropertyChanged method.

Conclusion

As an advocate of Aspect Oriented Programming and meta programming, I’m happy to see more native features move us closer to those methodologies. I’m not sure where Microsoft is headed with these types of features, but I feel that we could have gotten more from this release. I believe it’s going in the right direction and I’m excited to see how this grows. Maybe a way for developers to provide their own compile time transformations?

For more details, see the MSDN article: https://msdn.microsoft.com/en-us/library/hh534540.aspx

 

About the author

Dustin can be found on the road less traveled, avoiding what's popular. He's a co-host on the
MashThis.IO podcast, and a contributor to Pluralsight. He regularly attends user
groups, code camps and other developer events to speak about aspect oriented
programming and a range of other topics. When he isn't working or speaking at
events, he is preparing for his next project or speaking engagement.

Comments

  • Anonymous
    September 07, 2012
    Another downside to using reflection for this is that it may not give you what you expect. If the method has been inlined then at runtime you'll see the calling method instead.

  • Anonymous
    September 07, 2012
    That's a very good point, Daniel.

  • Anonymous
    September 07, 2012
    Now that I think about it, I wonder if using these attributes might prevent inlining.

  • Anonymous
    October 22, 2012
    That's nice. However, I'm looking forward to see funny code like this from some very enthusiastic programmers. if(callerName == "substract")   return a - b; else  return a + b;