博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
谈谈 INotifyPropertyChanged 的实现
阅读量:5993 次
发布时间:2019-06-20

本文共 4014 字,大约阅读时间需要 13 分钟。

INotifyPropertyChanged 接口是 WPF/Silverlight 开发中非常重要的接口, 它构成了 ViewModel 的基础, 数据绑定基本上都需要这个接口。 所以, 对它的实现也显得非常重要, 下面接贴出我知道的几种实现方式, 希望能起到抛砖引玉的作用。

一般的实现方式

这是一种再普通不过的实现方式, 代码如下:

1
2
3
4
5
6
7
8
9
10
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));
      
}
   
}
}

这种方式称之为一般的实现方式, 因为它确实是太普通不过了, 而且使用起来也让人感到厌恶, 因为必须指定手工指定属性名称:

1
2
3
4
5
6
7
8
9
10
11
12
public 
class 
MyViewModel : NotifyPropertyChanged {
 
   
private 
int 
_myField;
 
   
public 
int 
MyProperty {
      
get 
{
return 
_myField; }
      
set 
{
         
_myField = value;
         
OnPropertyChanged(
"MyProperty"
);
      
}
   
}
}

lambda 表达式实现方式

对 lambda 表达式比较熟悉的同学可以考虑用 lambda 表达式实现属性名称传递, 在 NotifyPropertyChanged 类添加一个这样的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 基类使用起来就令人舒服了很多:

1
2
3
4
5
6
7
8
9
10
11
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 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 对象时必须用帮助类来创建实例, 代码如下:

1
2
3
4
5
6
7
8
9
10
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 中, 提供了  标记, 利用这个属性, 可以将上面提供的 SetProperty 方法进行改造, 这样的实现才是最完美的:

1
2
3
4
5
6
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 标记助阵, 可以说使用起来是非常方便了:

1
2
3
4
5
6
7
8
9
10
11
public 
class 
MyViewModel : NotifyPropertyChanged {
 
   
private 
int 
_myField;
 
   
public 
int 
MyProperty {
      
get 
{
return 
_myField; }
      
set 
{
         
base
.SetProperty(
ref 
_myField, value);
      
}
   
}
}

这种方法虽然好,不过却只有在 .Net 4.5 中才有, 而且也许永远不会添加到 Silverlight 中。

所有文章遵循,要求署名、非商业 、保持一致。在满足的基础上可以转载,但请以超链接形式注明出处。

本博客已经迁移到 GitHub , 围观地址: 

本文转自张志敏博客园博客,原文链接:http://www.cnblogs.com/beginor/archive/2012/08/13/2636418.html
,如需转载请自行联系原作者
你可能感兴趣的文章
How to Customize UITabBar on iOS 5
查看>>
Java构建工具Ant小记(一)
查看>>
数据库oracle DBA SQL语句调优
查看>>
是时候
查看>>
存储过程
查看>>
HTML-part1
查看>>
070102_赌博设计:概率的基本概念,古典概型
查看>>
【转】Faster RCNN 原理
查看>>
centos6.5环境使用RPM包离线安装MariaDB 10.0.20
查看>>
centos 6.5环境利用iscsi搭建SAN网络存储服务及服务端target和客户端initiator配置详解...
查看>>
python操作三大主流数据库(10)python操作mongodb数据库④mongodb新闻项目实战
查看>>
从新手到专家的过程。
查看>>
QProcess 进程调用
查看>>
DataTable学习笔记2
查看>>
学习笔记之如果有人问你数据库的原理,叫他看这篇文章
查看>>
CCF NOI1034 钞票兑换
查看>>
UVA232 UVALive5171 POJ1888 Crossword Answers
查看>>
struts2 文件上传
查看>>
将长输入行折叠成若干较短的行
查看>>
【转】每个Java初学者都应该搞懂的六个问题
查看>>