Effective C# 学习笔记(四十三)使用Expression处理绑定(属性值更改)事件
2011-08-04 21:59
531 查看
DataBinding的实现原理是当其绑定的属性的值发生变化时将触发属性值改变事件,以调用数据绑定值改变后应触发的逻辑(如更新GridView列表中的数据展现)。其每个属性更改的区分是通过属性的名称来唯一标识的。这样的方法不利于代码的重构,也容易引起书写错误导致不易排查的运行时错误。使用Expression API和反射原理可以帮你解决属性变更触发事件的通用逻辑与属性名称之间的耦合。其实,LINQ TO SQL及 Entity
Framework就是构建在System.Linq.Expression的API上的。
下面的这段代码,构建了一个叫做MemoryMonitor的类型,其实现了INotifyPropertyChanged,
INotifyPropertyChanging接口。该类型拥有一个Timer属性(updater),其会周期性执行一个timerCallBack方法,该方法获取当前程序使用的内存数量,若内存使用数量有变,则将触发PropertyChanging和PropertyChanged事件,分别可以执行外部定义的属性修改前和属性修改后的委托。
public
class MemoryMonitor : INotifyPropertyChanged, INotifyPropertyChanging
{
System.Threading.Timer updater;
public MemoryMonitor()
{
updater = new
System.Threading.Timer((_) => timerCallback(_), null, 0, 5000);
}
private void timerCallback(object
unused)
{
UsedMemory =
GC.GetTotalMemory(false);
}
public long UsedMemory
{
get { return mem; }
private set
{
if (value != mem)
{
if (PropertyChanging != null)
PropertyChanging(this, new
PropertyChangingEventArgs("UsedMemory"));
mem
= value;
if
(PropertyChanged != null)
PropertyChanged(this, new
PropertyChangedEventArgs("UsedMemory"));
}
}
}
private long mem;
#region INotifyPropertyChanged Members
public event
PropertyChangedEventHandler PropertyChanged;
#endregion
#region INotifyPropertyChanging Members
public event
PropertyChangingEventHandler PropertyChanging;
#endregion
}
上边的代码存在的问题是,对于每一个随属性值改变触发事件的属性都要定义自己的事件触发逻辑(上面加粗的代码),而且对于每一个属性的事件触发的参数是字符串类的属性名称,如“UsedMemory”,这样容易在程序重构或程序员笔误时发生运行的错误,不易排查维护,这时就需要使用Expression 构建扩展方法来解耦属性更改触发事件对于字符串变量的依赖。
整体的改造思路如下:
检查属性的要赋予的新值是否于原始值相同。(若相同不处理)
触发INotifyPropertyChanging事件
对属性赋值
触发INotifyPropertyChanged事件
代码如下:
public
static class PropertyNotifyExtensions
{
//构建设置属性值改值前的事件参数的扩展方法
/// <summary>
/// 利用反射设置属性值
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="handler">设置后处理的事件</param>
/// <param
name="newValue">新值</param>
/// <param
name="oldValueExpression">属性原始值获取表达式</param>
/// <param
name="setter">属性设置委托</param>
/// <returns>设置的新值</returns>
public
static T SetNotifyProperty<T>(this PropertyChangedEventHandler handler,
T
newValue, Expression<Func<T>> oldValueExpression, Action<T>
setter)
{
return
SetNotifyProperty(handler, null, newValue, oldValueExpression, setter);
}
//设置带改值前和改值后的事件参数扩展方法
public
static T SetNotifyProperty<T>(this PropertyChangedEventHandler
postHandler,
PropertyChangingEventHandler
preHandler,
T
newValue,
Expression<Func<T>>
oldValueExpression,
Action<T>
setter)
{
Func<T>
getter = oldValueExpression.Compile();
T
oldValue = getter();
if
(!oldValue.Equals(newValue))
{
var body = oldValueExpression.Body as System.Linq.Expressions.MemberExpression;
var
propInfo = body.Member as PropertyInfo;
string propName = body.Member.Name;//动态获取了属性的名称
//
Get the target object
var targetExpression = body.Expression as ConstantExpression;
object target = targetExpression.Value;//获取要赋新值的对象
//处理前置事件
if
(preHandler != null)
preHandler(target,
new PropertyChangingEventArgs(propName));
//
Use Reflection to do the set:
//
propInfo.SetValue(target, newValue, null);
//var
compiledSetter = setter.Compile();
setter(newValue);//赋值
//处理后置事件
if
(postHandler != null)
postHandler(target,
new PropertyChangedEventArgs(propName));
}
return
newValue;
}
}
注意:上面的代码中没有添加异常处理的逻辑,在生产环境的代码中,你应该校验类型转换,setter是否存在等逻辑。
在使用的时候:
//
MemoryMonitor, using the extension methods
private
void timerCallback(object unused)
{
long
updatedValue = GC.GetTotalMemory(false);
//设置属性值处理,用扩展方法激活事件处理逻辑
PropertyChanged.SetNotifyProperty(updatedValue, () => UsedMemory2, (v) => UsedMemory2 = v);
}
public
long UsedMemory
{
get;
private
set;
}
Framework就是构建在System.Linq.Expression的API上的。
下面的这段代码,构建了一个叫做MemoryMonitor的类型,其实现了INotifyPropertyChanged,
INotifyPropertyChanging接口。该类型拥有一个Timer属性(updater),其会周期性执行一个timerCallBack方法,该方法获取当前程序使用的内存数量,若内存使用数量有变,则将触发PropertyChanging和PropertyChanged事件,分别可以执行外部定义的属性修改前和属性修改后的委托。
public
class MemoryMonitor : INotifyPropertyChanged, INotifyPropertyChanging
{
System.Threading.Timer updater;
public MemoryMonitor()
{
updater = new
System.Threading.Timer((_) => timerCallback(_), null, 0, 5000);
}
private void timerCallback(object
unused)
{
UsedMemory =
GC.GetTotalMemory(false);
}
public long UsedMemory
{
get { return mem; }
private set
{
if (value != mem)
{
if (PropertyChanging != null)
PropertyChanging(this, new
PropertyChangingEventArgs("UsedMemory"));
mem
= value;
if
(PropertyChanged != null)
PropertyChanged(this, new
PropertyChangedEventArgs("UsedMemory"));
}
}
}
private long mem;
#region INotifyPropertyChanged Members
public event
PropertyChangedEventHandler PropertyChanged;
#endregion
#region INotifyPropertyChanging Members
public event
PropertyChangingEventHandler PropertyChanging;
#endregion
}
上边的代码存在的问题是,对于每一个随属性值改变触发事件的属性都要定义自己的事件触发逻辑(上面加粗的代码),而且对于每一个属性的事件触发的参数是字符串类的属性名称,如“UsedMemory”,这样容易在程序重构或程序员笔误时发生运行的错误,不易排查维护,这时就需要使用Expression 构建扩展方法来解耦属性更改触发事件对于字符串变量的依赖。
整体的改造思路如下:
检查属性的要赋予的新值是否于原始值相同。(若相同不处理)
触发INotifyPropertyChanging事件
对属性赋值
触发INotifyPropertyChanged事件
代码如下:
public
static class PropertyNotifyExtensions
{
//构建设置属性值改值前的事件参数的扩展方法
/// <summary>
/// 利用反射设置属性值
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="handler">设置后处理的事件</param>
/// <param
name="newValue">新值</param>
/// <param
name="oldValueExpression">属性原始值获取表达式</param>
/// <param
name="setter">属性设置委托</param>
/// <returns>设置的新值</returns>
public
static T SetNotifyProperty<T>(this PropertyChangedEventHandler handler,
T
newValue, Expression<Func<T>> oldValueExpression, Action<T>
setter)
{
return
SetNotifyProperty(handler, null, newValue, oldValueExpression, setter);
}
//设置带改值前和改值后的事件参数扩展方法
public
static T SetNotifyProperty<T>(this PropertyChangedEventHandler
postHandler,
PropertyChangingEventHandler
preHandler,
T
newValue,
Expression<Func<T>>
oldValueExpression,
Action<T>
setter)
{
Func<T>
getter = oldValueExpression.Compile();
T
oldValue = getter();
if
(!oldValue.Equals(newValue))
{
var body = oldValueExpression.Body as System.Linq.Expressions.MemberExpression;
var
propInfo = body.Member as PropertyInfo;
string propName = body.Member.Name;//动态获取了属性的名称
//
Get the target object
var targetExpression = body.Expression as ConstantExpression;
object target = targetExpression.Value;//获取要赋新值的对象
//处理前置事件
if
(preHandler != null)
preHandler(target,
new PropertyChangingEventArgs(propName));
//
Use Reflection to do the set:
//
propInfo.SetValue(target, newValue, null);
//var
compiledSetter = setter.Compile();
setter(newValue);//赋值
//处理后置事件
if
(postHandler != null)
postHandler(target,
new PropertyChangedEventArgs(propName));
}
return
newValue;
}
}
注意:上面的代码中没有添加异常处理的逻辑,在生产环境的代码中,你应该校验类型转换,setter是否存在等逻辑。
在使用的时候:
//
MemoryMonitor, using the extension methods
private
void timerCallback(object unused)
{
long
updatedValue = GC.GetTotalMemory(false);
//设置属性值处理,用扩展方法激活事件处理逻辑
PropertyChanged.SetNotifyProperty(updatedValue, () => UsedMemory2, (v) => UsedMemory2 = v);
}
public
long UsedMemory
{
get;
private
set;
}
相关文章推荐
- Effective C# 学习笔记(四十)使用Dynamic处理匿名类型参数
- 2017.7.14 学习笔记 JQ选择器的使用及JS输入框事件及获取其相对属性值
- Effective C# 学习笔记(三十九) 使用Dynamic处理范型参数的运行时类型
- Python OpenCV学习笔记之:处理鼠标事件
- 【Java学习笔记】51:FocusEvent,KeyEvent,WindowEvent事件的处理
- JAVA(学习笔记1.2)——事件处理模型
- Effective C# 学习笔记(四)使用Conditional Attributes 替代 #if
- JavaScript事件处理程序 学习笔记
- js学习笔记(二):JQuery中的On绑定事件的方法
- ExtJS学习笔记2:响应事件、使用AJAX载入数据
- php学习笔记(十八)php扩展库mysqli的使用和处理结果集
- Cordova学习笔记 事件的简单使用
- react学习笔记 item6 --- 事件处理
- 窗口关闭 事件处理 -Java学习笔记(36)
- jQuery学习笔记:事件绑定
- Nginx学习笔记之事件驱动框架处理流程
- 学习笔记:使用HTTP处理程序监视指定目录请求
- JavaFX学习笔记八:处理触摸事件(高级)
- JavaScript事件处理程序 学习笔记
- 学习SWT的笔记 --键盘事件处理