Pages

Monday, October 7, 2013

Extend the INotifyPropertyChanged implemenation with a shorter setter

When a WPF application is needed, than probably a noticeable percentage of the code is property declaration, due MVVM, at least in the UI / client layer or in the contracts. Event with the method mentioned in one of my previous post the implementation costs 6-12 lines depends on how you break lines. After some testing I found a better way, a more elegant way to set a property value. However the LOC won't be less and the execution time will be increased it is also an option.
Stick to my old ViewModelBase class (if you don't know what I mean, you definitely have to read my previous post) let's extend it with a read only field to cache class Fields and initialize it in the constructor. It is Lazy because you don't necessary need it, only if the new SetAndRaisePropertyChanged is gonna be used.

private readonly Lazy<IEnumerable<FieldInfo>> Fields;

protected ViewModelBase()
{
  this.Fields = new Lazy<IEnumerable<FieldInfo>>(() => 
      this.GetType().GetFields(
          System.Reflection.BindingFlags.NonPublic | 
          System.Reflection.BindingFlags.Public | 
          System.Reflection.BindingFlags.Instance));
}

My goal was to define a method with only one parameter (the value). So, what I need is to figure out the property and the backing field. Since I use .NET4 [CallerMethodName] is not an option. But the StrackFrame and StrackTrace are always there.

private string ConvertPropertyNameToBackingField(string propertyName)
{
   return "_" + propertyName;
}

protected bool SetAndRaisePropertyChanged<TProperty>(TProperty value)
{
   var method = new StackFrame(1).GetMethod();
   if (method != null && method.Name.StartsWith("set_"))
   { 
      var propertyName = method.Name.Substring(4);
      var field = this.Fields.Value.SingleOrDefault(f => 
         f.Name == this.ConvertPropertyNameToBackingField(propertyName));

      if (field != null)
      {
         var oldValue = field.GetValue(this);
         var result = oldValue == null || oldValue.Equals(value) == false;

         if (result)
         {
            this.OnPropertyChangingHandler(propertyName, oldValue, value);

            field.SetValue(this, value);

            this.RaisePropertyChanged(propertyName);                  
         }

      return result;
      }

      throw new ArgumentException("Could not find backing field: "
          this.ConvertPropertyNameToBackingField(propertyName) + 
          " in class " + this.GetType().Name);
   }

   throw new InvalidOperationException(@"Simplified SetAndRaisePropertyChanged method 
     cannot be used outside of the property setter. In this case consider 
     to use the another overload with three parameters.");
}

And the usage?

private string _MyProperty;
public string MyProperty
{
   get { return _MyProperty; }
   set { this.SetAndRaisePropertyChanged(value); }
}

Peace of cake, isn't it? As you can see, it only works if you call it from the setter and there is some kind of naming convention.

And what are the numbers?

The very first setter calls in the ViewModel do not show a big differences. In case of 3 parameters it takes around 0.0015 seconds, while this one takes 0.002. Real differences are during 2nd, 3rd, 4th etc. call. With property selector, it only costs 0.00008, but this one needs around 0.0007, so factor ten. But if you really need performance you should consider to use the string version (without property selector).

No comments:

Post a Comment