WPF系列之三:实现类型安全的INotifyPropertyChanged接口,可以不用“Magic string” 么?

通常实现INotifyPropertyChanged接口很简单,为你的类只实现一个PropertyChanged 的Event就可以了。

例如实现一个简单的ViewModel1类:

 public class ViewModel1 : INotifyPropertyChanged
    {
        private string _data;
        public string Data
        {

            get { return _data; }
            set
            {
                if (_data == value)
                    return;
                _data = value;
                // Type un-safe PropertyChanged raise
                PropertyChanged(this, new PropertyChangedEventArgs("Data"));
            }
        }
        #region Implementation of INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = null;
        #endregion
    }
ViewModel1

上面是一个标准的实现一个可绑定的属性的方式,问题是其中使用了一个“Data”的字符串来表明发生变化的属性名。问题在于如果有人改变了属性的名字而忘记改变这里的字符串的话

,即使编译不会产生问题,但binding也会失败掉。最好的方法当然是当有人改变属性的名字时,编译器可以告诉我们这个地方也需要相应改变。

例如实现下面这样:

 private string _data;
        public string Data
        {

            get { return _data; }
            set
            {
                PropertyChanged.ChangeAndNotify(ref _data, value, () => Data);
            }
        }
类型安全的通知更新

让我们利用扩展方法来扩展PropertyChangedEventHandler实现这个目标:

   public static class PropertyChangedEventHandlerExtentions
    {
        public static bool ChangeAndNotify<T>(this PropertyChangedEventHandler handler, ref T field, T value, Expression<Func<T>> memberExpression)
        {
            if (memberExpression == null)
            {
                throw new ArgumentNullException("memberExpression");
            }
            // Retreive lambda body
            var body = memberExpression.Body as MemberExpression;
            if (body == null)
            {
                throw new ArgumentException("Lambda must return a property.");
            }
            if (EqualityComparer<T>.Default.Equals(field, value))
            {
                //值没有发生变化,不通知更新
                return false;
            }
            // Extract the right part (after "=>")
            var vmExpression = body.Expression as ConstantExpression;
            if (vmExpression != null)
            {
                // Create a reference to the calling object to pass it as the sender
                LambdaExpression lambda = Expression.Lambda(vmExpression);
                Delegate vmFunc = lambda.Compile();
                object sender = vmFunc.DynamicInvoke();

                if (handler != null)
                {
                    //body.Member.Name,Extract the name of the property to raise a change on
                    handler(sender, new PropertyChangedEventArgs(body.Member.Name));
                }
            }
            field = value;
            return true;
        }
    }
PropertyChangedEventHandlerExtentions

方法的核心思想就是1.利用扩展方法来扩展delegate:PropertyChangedEventHandler,2.用引用传值把原来的值传入扩展方法用来做比较,3.用lambda expression表达式树来传递待更新的属性名称来达到类型安全的目的,并且传递了调用这个lambda的对象实例(sender)。

如果上面的方法比较难理解,我觉得下面的方法也是可以接受的:

1.依然传递Magic string,但是可以简化值比较的代码:

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}
View Code

2.不用传递Magic string,也不用ExtentionMethod,只利用lambda expression来取得属性名:

 public class Data2 : INotifyPropertyChanged
    {
        // boiler-plate
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
        {
            if (selectorExpression == null)
                throw new ArgumentNullException("selectorExpression");
            MemberExpression body = selectorExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("The body must be a member expression");
            OnPropertyChanged(body.Member.Name);
        }
        protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(selectorExpression);
            return true;
        }
        // props
        private string name;
        public string Name
        {
            get { return name; }
            set { SetField(ref name, value, () => Name); }
        }
    }
View Code

完毕。

参考文章:

http://www.wpftutorial.net/INotifyPropertyChanged.html

http://blog.decarufel.net/2009/07/how-to-use-inotifypropertychanged-type_22.html

http://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist

作者:Andy Zeng

欢迎任何形式的转载,但请务必注明出处。

http://www.cnblogs.com/andyzeng/p/3710421.html

原文地址:https://www.cnblogs.com/andyzeng/p/3710421.html