谈谈 INotifyPropertyChanged 的实现
谈谈 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 表达式实现属性名称传递, 在 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 中。
- SQLite 预写式日志
- java调用.net asmx / wcf
- mybatis3.2.8 与 hibernate4.3.6 混用
- mybatis的物理分页:mybatis-paginator
- 使用 WMI 进行诊断WCF
- java:快速文件分割及合并
- 暴涨210倍的一个数字货币正悄无声息崛起
- QT Creator 快速入门教程 读书笔记(一)
- .NET程序优化(GCServer )
- redis 学习笔记(4)-HA高可用方案Sentinel配置
- oracle: job使用
- velocity模板引擎学习(2)-velocity tools 2.0
- java:如何用代码控制H2 Database启动
- 游戏开发完整学习路线(各个版本都有)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用Spring Data JPA进行数据分页与排序
- 搭建一个高可用负载均衡的集群架构(第二部分)
- 在PyTorch中使用Seq2Seq构建的神经机器翻译模型
- 理解 ECMAScript 规范(1)
- 使用 NodeJS 实现 JWT 原理
- 搭建一个高可用负载均衡的集群架构(第一部分)
- Java Stream函数式编程第三篇:管道流结果处理
- MySQL慢查询日志
- Pandas处理时间序列数据的20个关键知识点
- CentOS7下的LNMP环境搭建Dedecms网站
- 持久层框架JPA与Mybatis该如何选型
- 技术分享 | Online DDL 工具 pt-osc
- 基于hexo框架搭建个人博客【技术创作训练营】
- 对比讲解lambda表达式与传统接口函数实现方式
- 如何使用Java8 Stream API对Map按键或值进行排序