Tuesday, 2 April 2013

CallerMemberName .NET 4.0

After writing my recent post about a basic ViewModelBase implementation, the discussion about the CallerMemberNameAttribute got me thinking... how hard would it be to write my own implementation of this attribute?

As it turns out, it would be rather tricky!! My first thought was, we need to access the call stack if we want to get the name of the property that's called our method. Some 30 seconds research on Google brought me to the StackTrace and StackFrame classes which were both new to me. Brilliant, this will be easier than I thought, all I need to do now is create a custom attribute use these two new classes and a bit of reflection and I'll get the job done.

However it wasn't that simple, for those of you familiar with creating custom attributes (which I'm not) you will have noticed the flaw in my plan. Attributes aren't instantiated by default when you use the class, method, parameter or whatever they are applied to. The only way I can get the code in my custom attribute to be invoked is if use reflection to get the custom attributes of the item they're applied to, in this case it would be the parameter of my OnPropertyChanged method. It makes perfect sense when you think about it, attributes are meta data and it would be highly inefficient for the CLR to instantiate each attribute whenever an object is invoked or a method called.

So how does CallerMemberName work? A little more research and it was soon seeming fairly obvious. CallerMemberName belongs to the System.Runtime.CompilerServices, this is taken from MSDN to describe this namespace "provides functionality for compiler writers who use managed code to specify attributes in metadata that affect the run-time behavior of the common language runtime". Looks like CallerMemberName is used by the compiler which then embeds the string into the IL.

To confirm my suspicions I compiled my example code from my previous post, fired up IL Disassembler and inspected the code. There it was in the IL, the OnPropertyChanged method being called as if was passing the string of the property name through myself. So, if I want to implement my own .NET 4 CallerMemberNameAttribute I need to write my own compiler. Oh dear!

But don't worry all this effort wasn't in vein as there's more than one way to skin a cat. We can't feasibly produce the solution we want but we can provide a slightly less elegant solution that achieves the same goal. Using the SatckTrace and StackFrame classes mentioned earlier we can modify our code so our complete ViewModelBase looks like this:

  1. using System.ComponentModel;
  2. using System.Diagnostics;
  3. using System.Reflection;
  5. namespace JourneyIntoCode
  6. {
  7.     public abstract class ViewModelBase : INotifyPropertyChanged
  8.     {
  9.         #region INotifyPropertyChanged Members
  11.         public event PropertyChangedEventHandler PropertyChanged;
  13.         public void OnPropertyChanged(string propertyName = null)
  14.         {
  15.             if (propertyName == null)
  16.             {
  17.                 StackTrace stackTrace = new StackTrace();
  18.                 StackFrame frame = stackTrace.GetFrame(1);
  19.                 MethodBase method = frame.GetMethod();
  20.                 propertyName = method.Name.Replace("set_", "");
  21.             }
  23.             if (this.PropertyChanged != null)
  24.             {
  25.                 this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  26.             }
  27.         }
  29.         #endregion // INotifyPropertyChanged Members
  30.     }
  31. }

OnPropertyChanged has been modified so our propertyName parameter is now optional with a default of null. If this method is invoked without explicitly specifying a property name we are going to use StackTrace and StackFrame to obtain the calling member name use this as our property name. We instantiate StackTrace and call GetFrame passing through the index of the frame we want. The frame collection index is zero based, 0 being the currently executed frame, meaning if we pass a value of 1 as the index we'll get the next frame up, our calling frame. We then call GetMethod on the frame to return the System.Reflection.MethodBase and in turn use the Name property of the method to get our property name. When we create a property "XXXX" with get and set accessors, behind the scenes we automatically have "get_XXXX" and "set_XXXX" methods being created for us. It's this "set_XXXX" method that we're accessing here so we need to replace "set_" to get at our property name.

There we have it, we now have no need for the CheckPropertyName method as we can now call OnPropertyChanged from our ViewModel without a parameter as if we were calling the .NET 4.5 version using CallerMemberName.

No comments:

Post a Comment