更优雅地实现 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));
}
}
}
}
最终不修改版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));
}
}
}
}
代码基本上简洁到不能再简洁了,唯一的遗憾是ref,但是为了支持值类型,这个是没有办法的事情。
好的,到此为止大功告成。
如果大家有更好的方法,可以随意提出来,我们交流改进。


浙公网安备 33010602011771号