zoukankan      html  css  js  c++  java
  • [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,但是为了支持值类型,这个是没有办法的事情。

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

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

    作者:ColdJokeLife
    出处:http://www.cnblogs.com/ColdJokeLife/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,如有问题,请联系我,非常感谢。
  • 相关阅读:
    yii2.0的学习之旅(一)
    elasticSearch查询(一)
    PHP--常用配置项
    php7和php5区别是什么
    做社交电商的朋友注意了!芬香的竞‌品出‌来了,小绿券,跟芬‌香几‌乎一‌模一样,但是这‌个邀请码3RIOQQ是刚刚流出的,我这里是第一手!
    为什么强烈推荐 Java 程序员使用 Google Guava 编程!
    MyBatis动态SQL(认真看看, 以后写SQL就爽多了)
    微软宣布加入 OpenJDK,打不过就改变 Java 未来!
    IntelliJ 平台 2020 年路线图
    年底了,整理了一份程序员面试必看的文章合集
  • 原文地址:https://www.cnblogs.com/ColdJokeLife/p/3108112.html
Copyright © 2011-2022 走看看