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