[WPF 基础知识系列] —— 更优雅地实现 INotifyPropertyChanged

INotifyPropertyChanged 是WPF中非常重要的一个概念,它也是实现Binding 肯定要用到的一个接口(对于非Dependency Property而言)。

注:以下内容都是对普通对象而言的,Dependency Property有自己的通知机制,不需要额外去Notify。

因为它能够提供一个通知的功能,当我们修改了某个source对象后,就需要通知target,让target知道source修改了,然后进行update。

本文不是要讲INotifyPropertyChanged 具体如何使用,所以这里也就不贴代码展示了,如果你还不太清楚它的用途,

那么可以先看一下这篇博客:玩转INotifyPropertyChanged和ObservableCollection

本文,主要是想要介绍一下如何更加方便、优雅、简洁的来使用INotifyPropertyChanged。

因为我们要很频繁的使用INotifyPropertyChanged,特别是当写Model的对象时,这时就会有很多问题出现。

下面先介绍一下,最普通的Model对象。

// 版本1
public
class Person : INotifyPropertyChanged { private string name; public string Name { get { return name; } set { name = value; this.OnPropertyChanged("Name"); } }
  #region implement property changed
public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } }
  #endregion }

从上面这个类看来,有几个可以改进的地方:

1、如果我们每次创建一个Model类,那么我们就会重复很多代码(region区域内的代码),可以提取到一个另外的类中去;

  虽然,有时候必须使用接口(因为不允许继承多个类),但是对于Model类,一般不需要继承其他类,所以可以提取一个类出来。

2、每次我们创建一个属性时,就要在Set中OnPropertyChanged一下,而且这里使用一个字符串来代表名字,

  这样做不仅很麻烦,而且有时会造成很难察觉的错误(单词拼错了,造成Notify无效)。

 

根据上面的分析,我们可以修改一下这个Model:

1、提取到一个基类NotifyPropertyChangedEx中:

 
   
// 版本2
public abstact class NotifyPropertyChangedEx:INotifyPropertyChanged
    {

        #region Notify Property Changed

        public event PropertyChangedEventHandler PropertyChanged;



        public void NotifyPropertyChanged<T>(Expression<Func<T>> propertyName)

        {

            if (this.PropertyChanged != null)

            {

                var memberExpression = propertyName.Body as MemberExpression;

                if (memberExpression != null)

                {

                    OnPropertyChanged(memberExpression.Member.Name);

                }

            }

        }

        #endregion

    }

2、使用时,使用Lambda表达式代替之前的字符串传入属性名:

public class Person : NotifyPropertyChangedEx

{

    private string name;



    public string Name

    {

       get { return name; }

       set

       {

            name = value;

            this.NotifyPropertyChanged(() => Name);

       }

    }

}

现在来看,是不是简洁了不少,也优雅了不少呀!

但是对于一个程序猿来讲,这样还是不够完美。因为每次都要重复的写this.NotifyPropertyChanged(...);

所以,我们可以在进行一下修改。

我们再来根据我们的要求,再修改一次。

1、将Notify方法提取到父类的一个SetProperty方法中:

// 版本3

public abstract class ObservableObject : INotifyPropertyChanged

{

        public event PropertyChangedEventHandler PropertyChanged;



        protected void SetProperty<T>(ref T field, T value, Expression<Func<T>> expr)

        {

            if (!EqualityComparer<T>.Default.Equals(field, value))

            {

                field = value;

                var lambda = (LambdaExpression)expr;

                MemberExpression memberExpr;

                if (lambda.Body is UnaryExpression)

                {

                    var unaryExpr = (UnaryExpression)lambda.Body;

                    memberExpr = (MemberExpression)unaryExpr.Operand;

                }

                else

                {

                    memberExpr = (MemberExpression)lambda.Body;

                }

                var handler = this.PropertyChanged;

              if (handler != null)

              {

                  handler(this, new PropertyChangedEventArgs(memberExpr.Member.Name));

              }

            }

        }

}

2、在子类中,使用SetProperty方法来操作:

public class Person : NotifyPropertyChangedEx

{

    private string name;



    public string Name

    {

       get { return name; }

       set { this.SetProperty(ref name, value, ()=>Name); }

    }

}

现在来看,是不是更加简洁了。

当然,如果你使用C#5.0中的新特性CallerMemberName的话,将更加简单。因为连这个Lambda表达式也不再需要了。

下面,我来就要写一个基于CallerMemberName的例子吧。

1、使用[CallerMemberName]来替代Lambda表达式:

//C#5.0 版本
public
abstract class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) { return; } field = value; var handler = this.PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } }

2、现在,我们再来看一下,如何使用:

public class Person : NotifyPropertyChangedEx

{

    private string name;



    public string Name

    {

       get { return name; }

       set { this.SetProperty(ref name, value); }

    }

}

 

最终不修改版1:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Linq.Expressions;



namespace Contacts.Infrastructure

{

    public class ObservableObject : INotifyPropertyChanged

    {

        public event PropertyChangedEventHandler PropertyChanged = delegate { };



        public void SetProperty<TProperty>(ref TProperty field, TProperty value, Expression<Func<TProperty>> property)

        {

            if (!EqualityComparer<TProperty>.Default.Equals(field, value))

            {

                field = value;

                

                this.NotifyOfPropertyChange(property);

            }

        }



        /// <summary>

        /// Notifies subscribers of the property change.

        /// </summary>

        /// <param name="propertyName">Name of the property.</param>

        public virtual void NotifyOfPropertyChange(string propertyName)

        {

            this.RaisePropertyChangedEventCore(propertyName);

        }



        /// <summary>

        /// Notifies subscribers of the property change.

        /// </summary>

        /// <typeparam name="TProperty">The type of the property.</typeparam>

        /// <param name="property">The property expression.</param>

        public virtual void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)

        {

            var lambda = (LambdaExpression)property;

            MemberExpression memberExpression;

            var body = lambda.Body as UnaryExpression;

            if (body != null)

            {

                var unaryExpression = body;

                memberExpression = (MemberExpression)unaryExpression.Operand;

            }

            else

            {

                memberExpression = (MemberExpression)lambda.Body;

            }



            this.NotifyOfPropertyChange(memberExpression.Member.Name);

        }



        private void RaisePropertyChangedEventCore(string propertyName)

        {

            var handler = this.PropertyChanged;

            if (handler != null)

            {

                handler(this, new PropertyChangedEventArgs(propertyName));

            }

        }

    }

}
ObservableObject Final Version 1

 最终不修改版2:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Linq.Expressions;

using System.Runtime.CompilerServices;

using DrawdingBoard1.Annotations;



namespace DrawdingBoard1.Common

{

    public class ObservableObject : INotifyPropertyChanged

    {

        public event PropertyChangedEventHandler PropertyChanged;



        /// <summary>

        /// Notifies subscribers of the property change.

        /// </summary>

        /// <param name="propertyName">Name of the property, can be auto detected.</param>

        [NotifyPropertyChangedInvocator]

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)

        {

            RaisePropertyChangedEventCore(propertyName);

        }



        /// <summary>

        ///     Notifies subscribers of the property change.

        /// </summary>

        /// <param name="propertyName">Name of the property.</param>

        public virtual void NotifyPropertyChanged(string propertyName)

        {

            RaisePropertyChangedEventCore(propertyName);

        }



        /// <summary>

        ///     Notifies subscribers of the property change.

        /// </summary>

        /// <typeparam name="TProperty">The type of the property.</typeparam>

        /// <param name="property">The property expression.</param>

        public virtual void NotifyPropertyChanged<TProperty>(Expression<Func<TProperty>> property)

        {

            var lambda = (LambdaExpression) property;

            MemberExpression memberExpression;

            var body = lambda.Body as UnaryExpression;

            if (body != null)

            {

                var unaryExpression = body;

                memberExpression = (MemberExpression) unaryExpression.Operand;

            }

            else

            {

                memberExpression = (MemberExpression) lambda.Body;

            }



            NotifyPropertyChanged(memberExpression.Member.Name);

        }



        /// <summary>

        /// A set method which can notifies subscribers of the property change.

        /// </summary>

        /// <typeparam name="TProperty">The type of this property.</typeparam>

        /// <param name="field">The property.</param>

        /// <param name="value">The target value.</param>

        /// <param name="property">The property expression.</param>

        public void SetProperty<TProperty>(ref TProperty field, TProperty value, Expression<Func<TProperty>> property)

        {

            if (!EqualityComparer<TProperty>.Default.Equals(field, value))

            {

                field = value;

                NotifyPropertyChanged(property);

            }

        }



        /// <summary>

        /// A set method which can notifies subscribers of the property change.

        /// </summary>

        /// <typeparam name="TProperty">The type of this property.</typeparam>

        /// <param name="field">The property.</param>

        /// <param name="value">The target value.</param>

        /// <param name="propertyName">Name of the property, can be auto detected.</param>

        public void SetProperty<TProperty>(ref TProperty field, TProperty value, [CallerMemberName] string propertyName = null)

        {

            if (!EqualityComparer<TProperty>.Default.Equals(field, value))

            {

                field = value;

                RaisePropertyChangedEventCore(propertyName);

            }

        }



        /// <summary>

        /// Core method of raise property changed.

        /// </summary>

        /// <param name="propertyName">The name of property that will be notified.</param>

        private void RaisePropertyChangedEventCore(string propertyName)

        {

            var handler = PropertyChanged;

            if (handler != null)

            {

                handler(this, new PropertyChangedEventArgs(propertyName));

            }

        }

    }

}
ObservableObject Final Version 2

 

  

 

代码基本上简洁到不能再简洁了,唯一的遗憾是ref,但是为了支持值类型,这个是没有办法的事情。

好的,到此为止大功告成。

如果大家有更好的方法,可以随意提出来,我们交流改进。

 

 

 

你可能感兴趣的:(property)