談談 INotifyPropertyChanged 的實現
INotifyPropertyChanged 接口是 WPF/Silverlight 開發中非常重要的接口, 它構成了 ViewModel 的基礎, 數據綁定基本上都需要這個接口。 所以, 對它的實現也顯得非常重要, 下面接貼出我知道的幾種實現方式, 希望能起到拋磚引玉的作用。
一般的實現方式
這是一種再普通不過的實現方式, 代碼如下:
public class NotifyPropertyChanged : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
virtual internal protected void OnPropertyChanged(string propertyName) {
if (this.PropertyChanged != null) {
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
這種方式稱之為一般的實現方式, 因為它確實是太普通不過了, 而且使用起來也讓人感到厭惡, 因為必須指定手工指定屬性名稱:
public class MyViewModel : NotifyPropertyChanged {
private int _myField;
public int MyProperty {
get { return _myField; }
set {
_myField = value;
OnPropertyChanged("MyProperty");
}
}
}
lambda 表達式實現方式
對 lambda 表達式比較熟悉的同學可以考慮用 lambda 表達式實現屬性名稱傳遞, 在 NotifyPropertyChanged 類添加一個這樣的方法:
protected void SetProperty<T>(ref T propField, T value, Expression<Func<T>> expr) {
var bodyExpr = expr.Body as System.Linq.Expressions.MemberExpression;
if (bodyExpr == null) {
throw new ArgumentException("Expression must be a MemberExpression!", "expr");
}
var propInfo = bodyExpr.Member as PropertyInfo;
if (propInfo == null) {
throw new ArgumentException("Expression must be a PropertyExpression!", "expr");
}
var propName = propInfo.Name;
propField = value;
this.OnPropertyChanged(propName);
}
有了這個方法, NotifyPropertyChanged 基類使用起來就令人舒服了很多:
public class MyViewModel : NotifyPropertyChanged {
private int _myField;
public int MyProperty {
get { return _myField; }
set {
base.SetProperty(ref _myField, value, () => this.MyProperty);
}
}
}
這樣一來, 把屬性名稱用字符串傳遞改成了用 lambda 表達式傳遞, 減少了硬編碼, 確實方便了不少, 但是還是感覺略微麻煩了一些, 還是要寫一個 lambda 表達式來傳遞屬性名稱。
攔截方式實現
如果對 Castal.DynamicProxy 有印象的話, 可以考慮使用 DynamicProxy 進行攔截實現, 我的實現如下:
// 1. 先定義一個攔截器, 重寫 PostProcess 方法, 當發現是調用以 set_ 開頭的方法時,
// 一般就是設置屬性了, 可以在這里觸發相應的事件。
internal class NotifyPropertyChangedInterceptor : StandardInterceptor {
protected override void PostProceed(IInvocation invocation) {
base.PostProceed(invocation);
var methodName = invocation.Method.Name;
if (methodName.StartsWith("set_")) {
var propertyName = methodName.Substring(4);
var target = invocation.Proxy as NotifyPropertyChanged;
if (target != null) {
target.OnPropertyChanged(propertyName);
}
}
}
}
// 2. 再定義一個幫助類, 提供一個工廠方法創建代理類。
public static class ViewModelHelper {
private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();
private static readonly NotifyPropertyChangedInterceptor Interceptor
= new NotifyPropertyChangedInterceptor();
public static T CreateProxy<T>(T obj) where T : class, INotifyPropertyChanged {
return ProxyGenerator.CreateClassProxyWithTarget(obj, Interceptor);
}
}
使用起來也是很方便的, 只是創建 ViewModel 對象時必須用幫助類來創建實例, 代碼如下:
public class MyViewModel : NotifyPropertyChanged {
// 定義屬性時不需要任何基類方法, 和普通屬性沒有什么兩樣。
public int MyProperty {
get; set;
}
}
// 使用時需要這樣創建實例:
var viewModel = ViewModelHelper.CreateProxy<MyViewModel>();
viewModel.MyProperty = 100;
不過這種實現的缺點就是所有的屬性都會觸發 PropertyChanged 事件, 而且只能觸發一個事件, 而在實際開發中, 偶爾需要設置一個屬性, 觸發多個 PropertyChanged 事件。
未來 .Net 4.5 的實現方式
在即將發布的 .Net 4.5 中, 提供了 CallerMemberNameAttribute 標記, 利用這個屬性, 可以將上面提供的 SetProperty 方法進行改造, 這樣的實現才是最完美的:
protected void SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null) {
if (object.Equals(storage, value)) return;
storage = value;
this.OnPropertyChanged(propertyName);
}
由于有了 CallerMemberName 標記助陣, 可以說使用起來是非常方便了:
public class MyViewModel : NotifyPropertyChanged {
private int _myField;
public int MyProperty {
get { return _myField; }
set {
base.SetProperty(ref _myField, value);
}
}
}
這種方法雖然好,不過卻只有在 .Net 4.5 中才有, 而且也許永遠不會添加到 Silverlight 中。
張志敏所有文章遵循創作共用版權協議,要求署名、非商業 、保持一致。在滿足創作共用版權協議的基礎上可以轉載,但請以超鏈接形式注明出處。
本博客已經遷移到 GitHub , 圍觀地址: https://beginor.github.io/
浙公網安備 33010602011771號