Monday, July 13, 2009

INotifyPropertyChanged, C# lambdas, MethodBase.GetCurrentMethod()

I'm on a WPF project at the moment, and Silverlight before that. Both have this notion of INotifyPropertyChanged for bindings, whose only requirement is that you implement
event System.ComponentModel.PropertyChangedEventHandler PropertyChanged
and fire it off whenever any of your properties changes. So far so simple.

The downside is, the PropertyChangedEventArgs payload is not just a typewashed object, it's merely a string of the name of your property, so you see a lot of

public Foo Bar
{
get { return _bar; }
set
{
_bar = value;
OnNotifyPropertyChanged("Bar");
}
}

which works dandy until you rename something or forget to update your copy-and-paste. ReSharper will look in your literals for you, but I'm usually too impatient and sloppy to trust that.

Instead, I've been on a kick to use lambdas and MethodBase.GetCurrentMethod().

First you need a base class like ViewModelBase. Add the following:





   1:   

   2:          protected bool Set<TProperty>(Expression<Func<TViewModelInterface, TProperty>> expression, TProperty newValue)

   3:          {

   4:              TProperty oldValue = _propertyBag.Get(expression);

   5:              string propertyName = expression.GetPropertyName();

   6:              return SetObservable(oldValue, newValue, t => _propertyBag.Set(expression, t), propertyName);

   7:          }

   8:   

   9:          protected bool Set<TProperty>(MethodBase setter, TProperty newValue)

  10:          {

  11:              TProperty oldValue = _propertyBag.Get<TProperty>(setter);

  12:              string propertyName = setter.GetPropertyName();

  13:              return SetObservable(oldValue, newValue, t => _propertyBag.Set(setter, t), propertyName);

  14:          }

  15:   

  16:          private bool SetObservable<T>(T oldValue, T newValue, Action<T> updater, string propertyName)

  17:          {

  18:              bool changed = HasChanged(oldValue, newValue);

  19:              if (changed)

  20:              {

  21:                  updater.Invoke(newValue);

  22:                  // don't fire property change until oldValue is updated

  23:                  OnPropertyChanged(propertyName);

  24:              }

  25:              return changed;

  26:          }

  27:   

  28:          private static bool HasChanged<T>(T oldValue, T newValue)

  29:          {

  30:              bool changed;

  31:              if (ReferenceEquals(null, oldValue))

  32:              {

  33:                  changed = !ReferenceEquals(null, newValue);

  34:              }

  35:              else

  36:              {

  37:                  changed = !oldValue.Equals(newValue);

  38:              }

  39:              return changed;

  40:          }



Somewhere else are your helpful extensions:



   1:   

   2:          public const string GetterPrefix = "get_";

   3:          public const string SetterPrefix = "set_";

   4:          private static readonly int _prefixLength = GetterPrefix.Length;

   5:          

   6:          public static IList<Type> GetInheritedInterfaces(this Type type)

   7:          {

   8:              IList<Type> direct = type.GetInterfaces();

   9:              IList<Type> results = direct;

  10:              foreach (Type implemented in direct)

  11:              {

  12:                  results = results.Union(implemented.GetInheritedInterfaces()).ToList();

  13:              }

  14:              return results;

  15:          }

  16:   

  17:          public static string GetPropertyName<TInstance, TProperty>(this Expression<Func<TInstance, TProperty>> expression)

  18:          {

  19:              return ReflectionHelper.GetProperty(expression).Name;

  20:          }

  21:   

  22:          public static string GetPropertyName(this MethodBase method)

  23:          {

  24:              if (method.Name.StartsWith(GetterPrefix)

  25:                  || method.Name.StartsWith(SetterPrefix))

  26:              {

  27:                  return method.Name.Substring(_prefixLength);

  28:              }

  29:              throw new ArgumentException("Method was neither a setter or a getter.", "method");

  30:          }



where ReflectionHelper is basically lifted wholesale from Fluent NHibernate under BSD. It's not rocket science, though, just some manipulation of C# expression trees to extract the name of a property from a lambda that uses it.

Also, _propertyBag is basically a Dictionary where the strings are the names of the properties. Actually it's a Dictionary to keep some other stuff like original values, dirty bits, etc., but that's another topic.

The implementation of Get is like Set, only simpler.

The net-net of all this is that you can have

interface IMyClass
{
Foo Bar { get; set; }
}

class MyClass
{
public Foo Bar
{
get { return Get(x => x.Bar); }
set { Set(MethodBase.GetCurrentMethod(), value); }
}
}

which is fewer lines, no backing variable (e.g, _bar), and ReSharper-izable. Plus it breaks at compile time until you expose Bar on your interface, so it's as close to idiotproof as I've been able to get so far.


No comments: