Concise PropertyChanged Implementation






4.25/5 (7 votes)
Save wear and tear on your keyboard: INotifyPropertyChanged with the fewest key-strokes.
I H8 PropertyChanged
...because I got used to auto-properties really quickly...
The most painful part of building entities to interact with the UI, (for me at least) is having to create properties that participate in the INotifyPropertyChanged
interface.
You know the ones I mean:
string _backingField;
public string MyProperty
{
get { return _backingField; }
set
{
if (!string.Equals(value, _backingField)
{
_backingField = value;
OnPropertyChanged("MyProperty");
}
}
}
You can improve the rate at which you can add these using a code-snippet, but there are annoying limitations, like having to rename the backing field to match the naming convention, the problem of the name being passed to OnPropertyChanged
becoming out-of-date with refactoring *(unless you are using C#6.0/VS2015 and "nameof()
") and that it's too verbose, you end up with a bunch of backing fields you don't want, and it's just fugly.
Wouldn't it be nicer if it looked like this?
public string MyProperty
{
get { return GetValue(() => MyProperty); }
set { SetValue(() => MyProperty, value); }
}
The above code can be generated easily enough with a snippet, it neatly aligns, requires no backing field, type-inference and generics work together to make it strongly-typed, and refactoring the property-name will not break anything.
What's the Trick?
Expressions! The GetValue
and SetValue
method's first parameter is an Expression<Func<T>>
- this allows you to pass a lambda expression into the method (with the property name) and supplies the generic-type context (T)
The rest of the trick is that the backing field is in fact a Dictionary<string, object>
that stores the values for all the properties.
/// <summary>
/// stores the values for the properties
/// </summary>
protected Dictionary<string, object> m_values = new Dictionary<string, object>();
/// <summary>
/// gets the value of the member specified in the member expression.
/// Generic type parameters should be inferred.
/// </summary>
/// <typeparam name="T">the property type</typeparam>
/// <param name="memberExpression">lambda expression
/// referring to the property invoking this method</param>
/// <returns>the value, or default(T)</returns>
public T GetValue<T>(Expression<Func<T>> memberExpression)
{
var body = memberExpression.Body as MemberExpression;
if (body != null)
{
object value;
if (m_values.TryGetValue(body.Member.Name, out value))
{
// return the value;
return (T)value;
}
}
// return a default:
return default(T);
}
/// <summary>
/// sets the value
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="memberExpression"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool SetValue<T>(Expression<Func<T>> memberExpression, T value)
{
// is the value different from the existing?
if (EqualityComparer<T>.Default.Equals(value, GetValue(memberExpression)))
{
return false;
}
// fetch the name of the property:
var body = memberExpression.Body as MemberExpression;
if (body != null)
{
// set the value:
m_values[body.Member.Name] = value;
// raise the property-changed event
OnPropertyChanged(body.Member.Name);
}
// return true for changed:
return true;
}