简洁的PropertyChanged实现






4.25/5 (7投票s)
节省键盘磨损:使用最少的按键实现INotifyPropertyChanged。
我讨厌PropertyChanged
……因为我很快就习惯了自动属性……
构建与UI交互的实体时,最痛苦的部分(至少对我来说)是必须创建参与INotifyPropertyChanged
接口的属性。
你知道我指的是哪些
string _backingField;
public string MyProperty
{
get { return _backingField; }
set
{
if (!string.Equals(value, _backingField)
{
_backingField = value;
OnPropertyChanged("MyProperty");
}
}
}
你可以使用代码片段来提高添加这些属性的速度,但存在一些令人讨厌的限制,例如必须重命名备份字段以匹配命名约定,OnPropertyChanged
传递的名称在重构时可能失效 *(除非你使用的是C#6.0/VS2015和“nameof()
”),而且它过于冗长,最终会得到一堆你不需要的备份字段,而且看起来很丑陋。
如果它看起来像这样,不是更好吗?
public string MyProperty
{
get { return GetValue(() => MyProperty); }
set { SetValue(() => MyProperty, value); }
}
上面的代码可以使用代码片段轻松生成,它整齐对齐,不需要备份字段,类型推断和泛型协同工作以使其具有强类型,并且重构属性名称不会破坏任何东西。
诀窍是什么?
表达式!GetValue
和SetValue
方法的第一参数是一个Expression<Func<T>>
- 这允许你将lambda表达式传递到该方法中(包含属性名称),并提供泛型类型上下文 (T)
剩下的诀窍是,备份字段实际上是一个Dictionary<string, object>
,它存储所有属性的值。
/// <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;
}