POCO 对象上的 INotifyPropertyChanged 自动实现






4.92/5 (24投票s)
使用自定义代理生成器自动实现 INotifyPropertyChanged
引言
在 WPF 中使用数据绑定需要使用并实现 INotifyPropertyChanged
接口。这项任务通常很无聊,甚至不安全,因为属性必须通过简单的 string
字符串按名称指定。可以使用 lambda 表达式解决按 string
属性的问题,因此我们可以找到一些帮助方法,允许我们使用一些语法糖,例如...
NotifyPropertyChanged((NotifyPropertyChanged(()=>MyProperty);
...但我们需要从某个特殊的类派生我们的 ViewModel
,并且在任何情况下,我们都必须在每次设置属性时编写一些无聊的代码。为了避免编写此类内容,我们可以使用 AOP 来拦截属性设置器,但这通常涉及将一些第三方库添加到我们的软件包中,有时这是一个问题。在本文中,我们将看到一个使用 AOP 解决此问题的方案,但这种情况非常特殊,我们只需要在解决方案中嵌入几个类即可解决问题。
背景
使用 INotifyPropertyChanged
的原始方式
public class Customer :INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set {
if(value!=name)
OnPropertyChanged("Name");
name = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
使用 AutoNotifyPropertyChange
帮助类,我们可以将上述代码简化为
public class Customer
{
public virtual string Name
}
更简单,不是吗?
为了用我们所讨论的简单库完成这项工作,必须满足以下要求才能实现结果
ViewModel
类必须是public
- 要通知更改的属性必须是
public
和virtual
仅当通过 public
接口更改属性时,才会发生通知,代理对内部后备字段一无所知。
如果需要自定义实现 INotifyPropertyChanged
,我们可以将一个实现 INotifyPropertyChanged
的类传递给代理生成器,但在这种情况下,我们必须确保存在一个名为 OnPropertyChanged(string propertyName)
的 public
或 protected
函数触发该事件。代理生成器假定函数名称并假设实现通过以适当且一致的方式触发 PropertyChange
事件来工作。下面是一个已经实现 INotifyPropertyChanged
的模型类
public class ModelSample:INotifyPropertyChanged
{
public virtual int MyProperty1 { get; set; }
public virtual double MyProperty2 { get; set; }
public virtual DateTime MyProperty3 { get; set; }
public float NoNotify { get; set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
/* Since this class already implements INotifyPropertyChanged
* having this function is mandatory
*/
protected virtual void OnPropertyChanged(string property)
{
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
以下代码从 Customer
派生一个类,并在每次我们使用不同的值设置属性值时将代码连接到触发 PropertyChanged
事件。请注意,TypeFactory
类在某种程度上具有容错性。如果实现了接口,则必须实现 OnPropertyChanged(string propName)
。我们可以利用容错性来避免某些属性的通知:只需将它们声明为 non virtual。
Customer model = Activator.CreateInstance(
TypeFactory.AutoNotifier<Customer>()
);
工作原理
AutoNotifyPropertyChange.TypeFactory
类在内部使用 CodeDom 生成模型的子类,并将代码连接到属性设置器中。类在内部编译并作为类型返回。
让我们看看这个例子(POCO)类
public class ClockViewModel
{
public virtual int Hour { get; set; }
public virtual int Minute { get; set; }
public virtual int Second { get; set; }
public virtual int Millisecond { get; set; }
public virtual int Centiseconds { get { return Millisecond / 10; } }
}
并查看自动生成的类(您永远不必使用此代码,它只是为了展示幕后发生的事情)
namespace @__autonotifypropertychanged
{
internal class @__autonotifyClockViewModel : Autonotify.Demo.ClockViewModel,
System.ComponentModel.INotifyPropertyChanged
{
public override int Hour
{
get
{
return base.Hour;
}
set
{
if ((false == base.Hour.Equals(value)))
{
base.Hour = value;
this.OnPropertyChanged("Hour");
}
}
}
public override int Minute
{
get
{
return base.Minute;
}
set
{
if ((false == base.Minute.Equals(value)))
{
base.Minute = value;
this.OnPropertyChanged("Minute");
}
}
}
public override int Second
{
get
{
return base.Second;
}
set
{
if ((false == base.Second.Equals(value)))
{
base.Second = value;
this.OnPropertyChanged("Second");
}
}
}
public override int Millisecond
{
get
{
return base.Millisecond;
}
set
{
if ((false == base.Millisecond.Equals(value)))
{
base.Millisecond = value;
this.OnPropertyChanged("Millisecond");
this.OnPropertyChanged("Centiseconds");
}
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler handler;
handler = this.PropertyChanged;
if ((null != handler))
{
handler(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
}
所有管道工作都已完成,您将获得一个实现 INotifyPropertyChange
的代理。您可能注意到属性“Centiseconds
”是一个只读的计算属性,但在生成的代码中,我们看到事件在正确的位置被触发:更改毫秒也会强制更改 Centiseconds
属性。这是因为代理生成器在内部会调查每个属性 getter 的代码,以查看它们是否使用其他属性值,如果是,则该属性被视为依赖项,会触发适当的事件。inflector 不会查看代码行为,它只是假设如果使用了 getter,那么这会影响结果(这是正确的,只要我们不使用 getter 中的属性值来产生一些副作用,在这种情况下,会不合理地触发更改事件,但在这种情况下,这可能不是问题)。
要创建代理,只需编写以下内容
var obj = Activator.CreateInstance(
AutoNotifyPropertyChange.TypeFactory.AutoNotifier<ClockViewModel>() );
自动生成的类在内部被缓存,因此多次请求同一类型不需要代理代码重新创建和重建。
关注点
代码被编译成一个 DLL,但代理的类只有几个,可以无痛地合并到现有的解决方案代码中,没有任何额外的依赖项。在提供的示例应用程序中,使用 ClockViewModel
显示了与简单视图的绑定。
历史
- 2010 年 12 月 30 日:初始版本
- 2011 年 1 月 4 日:文章更新