在没有魔法字符串的情况下实现 NotifyPropertyChanged [英] Implementing NotifyPropertyChanged without magic strings

查看:29
本文介绍了在没有魔法字符串的情况下实现 NotifyPropertyChanged的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

I'm working on a large team application which is suffering from heavy use of magic strings in the form of NotifyPropertyChanged("PropertyName"), - the standard implementation when consulting Microsoft. We're also suffering from a great number of misnamed properties (working with an object model for a computation module that has hundreds of stored calculated properties) - all of which are bound to the UI.

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

My team experiences many bugs related to property name changes leading to incorrect magic strings and breaking bindings. I wish to solve the problem by implementing property changed notifications without using magic strings. The only solutions I've found for .Net 3.5 involve lambda expressions. (for example: Implementing INotifyPropertyChanged - does a better way exist?)

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

My manager is extremely worried about the performance cost of switching from

set { ... OnPropertyChanged("PropertyName"); }

set {  ... OnPropertyChanged(() => PropertyName); }

从哪里提取名称

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);
}

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

Consider an application like a spreadsheet where when a parameter changes, approximately a hundred values are recalculated and updated on the UI in real-time. Is making this change so expensive that it will impact the responsiveness of the UI? I can't even justify testing this change right now because it would take about 2 days worth of updating property setters in various projects and classes.

推荐答案

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

I did a thorough test of NotifyPropertyChanged to establish the impact of switching to the lambda expressions.

这是我的测试结果:

如您所见,使用 lambda 表达式大约比普通硬编码字符串属性更改实现慢 5 倍,但用户不必担心,因为即便如此,它也能够每秒产生 10 万次属性更改在我不太特别的工作电脑上.因此,不再需要对字符串进行硬编码并能够拥有处理所有业务的单行 setter 所获得的好处远远超过了我的性能成本.

As you can see, using the lambda expression is roughly 5 times slower than the plain hard-coded string property change implementation, but users shouldn't fret, because even then it's capable of pumping out a hundred thousand property changes per second on my not so special work computer. As such, the benefit gained from no longer having to hard-code strings and being able to have one-line setters that take care of all your business far outweighs the performance cost to me.

测试 1 使用标准的 setter 实现,并检查属性是否实际发生了变化:

Test 1 used the standard setter implementation, with a check to see that the property had actually changed:

    public UInt64 TestValue1
    {
        get { return testValue1; }
        set
        {
            if (value != testValue1)
            {
                testValue1 = value;
                InvokePropertyChanged("TestValue1");
            }
        }
    }

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

Test 2 was very similar, with the addition of a feature allowing the event to track the old value and the new value. Because this features was going to be implicit in my new base setter method, I wanted to see how much of the new overhead was due to that feature:

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

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

Test 3 was where the rubber met the road, and I get to show off this new beautiful syntax for performing all observable property actions in one line:

    public UInt64 TestValue3
    {
        get { return testValue3; }
        set { SetNotifyingProperty(() => TestValue3, ref testValue3, value); }
    }

实施

在所有 ViewModel 最终继承的 BindingObjectBase 类中,是驱动新功能的实现.我已经去掉了错误处理,所以函数的内容很清楚:

In my BindingObjectBase class, which all ViewModels end up inheriting, lies the implementation driving the new feature. I've stripped out the error handling so the meat of the function is clear:

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;
}

这三种方法都满足 OnPropertyChanged 例程,这仍然是标准:

All three methods meet at the OnPropertyChanged routine, which is still the standard:

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

奖金

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

If anyone's curious, the PropertyChangedExtendedEventArgs is something I just came up with to extend the standard PropertyChangedEventArgs, so an instance of the extension can always be in place of the base. It leverages knowledge of the old value when a property is changed using SetNotifyingProperty, and makes this information available to the handler.

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;
    }
}

这篇关于在没有魔法字符串的情况下实现 NotifyPropertyChanged的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆