65.9K
CodeProject 正在变化。 阅读更多。
Home

POCO 对象上的 INotifyPropertyChanged 自动实现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (24投票s)

2011年1月1日

CPOL

4分钟阅读

viewsIcon

120854

downloadIcon

2326

使用自定义代理生成器自动实现 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
}

更简单,不是吗?

为了用我们所讨论的简单库完成这项工作,必须满足以下要求才能实现结果

  1. ViewModel 类必须是 public
  2. 要通知更改的属性必须是 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 日:文章更新
© . All rights reserved.