在没有魔术字符串的情况下实现NotifyPropertyChanged

Ala*_*ain 17 wpf binding inotifypropertychanged .net-3.5

可能重复:
使用linq表达式的typesafe NotifyPropertyChanged

我正在研究一个庞大的团队应用程序,该应用程序正在以大量使用魔术字符串的形式NotifyPropertyChanged("PropertyName")- 在咨询Microsoft时的标准实现.我们还遭受了大量错误命名的属性(使用具有数百个存储的计算属性的计算模块的对象模型) - 所有这些属性都绑定到UI.

我的团队经历了许多与属性名称更改相关的错误,导致错误的魔术字符串和破坏绑定.我希望通过实现属性更改通知而不使用魔术字符串来解决问题.我发现.Net 3.5的唯一解决方案涉及lambda表达式.(例如:实现INotifyPropertyChanged - 是否存在更好的方法?)

我的经理非常担心转换的性能成本

set { ... OnPropertyChanged("PropertyName"); }
Run Code Online (Sandbox Code Playgroud)

set {  ... OnPropertyChanged(() => PropertyName); }
Run Code Online (Sandbox Code Playgroud)

提取名称的位置

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
    MemberExpression body = selectorExpression.Body as MemberExpression;
    if (body == null) throw new ArgumentException("The body must be a member expression");
    OnPropertyChanged(body.Member.Name);
}
Run Code Online (Sandbox Code Playgroud)

考虑像电子表格这样的应用程序,当参数发生变化时,会在UI上实时重新计算和更新大约一百个值.是否会使此更改变得如此昂贵以至于会影响UI的响应能力?我现在甚至无法证明测试此更改的合理性,因为在各种项目和类中更新属性设置器需要大约2天.

Ala*_*ain 23

我对NotifyPropertyChanged进行了全面测试,以确定切换到lambda表达式的影响.

这是我的测试结果:

在此输入图像描述

正如您所看到的,使用lambda表达式大约比普通硬编码字符串属性更改实现慢5倍,但是用户不应该担心,因为即使这样,它也能够在我的每秒上输出数十万个属性更改.这么特别的工作电脑.因此,不再需要硬编码字符串以及能够使用一线安装程序来处理所有业务所带来的好处远远超过了我的性能成本.

测试1使用标准的setter实现,并检查该属性是否已实际更改:

    public UInt64 TestValue1
    {
        get { return testValue1; }
        set
        {
            if (value != testValue1)
            {
                testValue1 = value;
                InvokePropertyChanged("TestValue1");
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

测试2非常相似,添加了一个允许事件跟踪旧值和新值的功能.因为这个功能将隐含在我的新基本setter方法中,所以我想看看有多少新开销是由于该功能造成的:

    public UInt64 TestValue2
    {
        get { return testValue2; }
        set
        {
            if (value != testValue2)
            {
                UInt64 temp = testValue2;
                testValue2 = value;
                InvokePropertyChanged("TestValue2", temp, testValue2);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

测试3是橡胶遇到的道路,我开始展示这种新的漂亮语法,用于在一行中执行所有可观察的属性操作:

    public UInt64 TestValue3
    {
        get { return testValue3; }
        set { SetNotifyingProperty(() => TestValue3, ref testValue3, value); }
    }
Run Code Online (Sandbox Code Playgroud)

履行

在我的BindingObjectBase类中,所有ViewModel最终继承的都是驱动新功能的实现.我已经删除了错误处理,因此函数的内容很明确:

protected void SetNotifyingProperty<T>(Expression<Func<T>> expression, ref T field, T value)
{
    if (field == null || !field.Equals(value))
    {
        T oldValue = field;
        field = value;
        OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(GetPropertyName(expression), oldValue, value));
    }
}
protected string GetPropertyName<T>(Expression<Func<T>> expression)
{
    MemberExpression memberExpression = (MemberExpression)expression.Body;
    return memberExpression.Member.Name;
}
Run Code Online (Sandbox Code Playgroud)

所有三种方法都符合OnPropertyChanged例程,这仍然是标准:

public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
        handler(sender, e);
}
Run Code Online (Sandbox Code Playgroud)

奖金

如果有人好奇的话,PropertyChangedExtendedEventArgs是我刚刚提出的扩展标准PropertyChangedEventArgs的东西,因此扩展的实例总是可以代替基础.当使用SetNotifyingProperty更改属性时,它会利用旧值的知识,并使此信息可供处理程序使用.

public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
    public virtual T OldValue { get; private set; }
    public virtual T NewValue { get; private set; }

    public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)
        : base(propertyName)
    {
        OldValue = oldValue;
        NewValue = newValue;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 回复:_“它每秒能够泵出十万个属性更改” _。我同意这可能绰绰有余。但是,仅作记录,请注意,以一秒钟的时间窗口来表示实现速度可能不是很有意义:`INotifyPropertyChanged`主要用于UI代码,您希望将可察觉的延迟保持在最低水平;人类的反应时间约为1/10秒。因此,一个实现必须在50-100毫秒的时间范围内做得足够好。 (2认同)