代码改变世界

Effective C# 学习笔记(四十三)使用Expression处理绑定(属性值更改)事件

2011-08-04 21:59  小郝(Kaibo Hao)  阅读(781)  评论(1编辑  收藏  举报

DataBinding的实现原理是当其绑定的属性的值发生变化时将触发属性值改变事件,以调用数据绑定值改变后应触发的逻辑(如更新GridView列表中的数据展现)。其每个属性更改的区分是通过属性的名称来唯一标识的。这样的方法不利于代码的重构,也容易引起书写错误导致不易排查的运行时错误。使用Expression API和反射原理可以帮你解决属性变更触发事件的通用逻辑与属性名称之间的耦合。其实,LINQ TO SQL及 Entity Framework就是构建在System.Linq.ExpressionAPI上的。

 

下面的这段代码,构建了一个叫做MemoryMonitor的类型,其实现了INotifyPropertyChanged, INotifyPropertyChanging接口。该类型拥有一个Timer属性(updater),其会周期性执行一个timerCallBack方法,该方法获取当前程序使用的内存数量,若内存使用数量有变,则将触发PropertyChangingPropertyChanged事件,分别可以执行外部定义的属性修改前和属性修改后的委托。

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 构建扩展方法来解耦属性更改触发事件对于字符串变量的依赖。

 

整体的改造思路如下:

  1. 检查属性的要赋予的新值是否于原始值相同。(若相同不处理)
  2. 触发INotifyPropertyChanging事件
  3. 对属性赋值
  4. 触发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;

}