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
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
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:
Post a Comment