NotifyPropertyChanged 事件,其中事件参数包含旧值 [英] NotifyPropertyChanged event where event args contain the old value

查看:31
本文介绍了NotifyPropertyChanged 事件,其中事件参数包含旧值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有类似 INotifyPropertyChanged 的​​接口,其中事件 args 包含正在更改的属性的旧值,或者我是否必须扩展该接口以创建一个?

Is there an INotifyPropertyChanged-like interface where the event args contains the old value of the property being changed, or do I have to extend that interface to create one?

例如:

    public String ProcessDescription
    {
        get { return _ProcessDescription; }
        set
        {
            if( value != ProcessDescription )
            {
                String oldValue = _ProcessDescription;
                _ProcessDescription = value;
                InvokePropertyChanged("ProcessDescription", oldvalue);
            }
        }
    }

    InvokePropertyChanged(String PropertyName, OldValue)
    {
         this.PropertyChanged( new ExtendedPropertyChangedEventArgs(PropertyName, OldValue) );
    }

我也愿意使用类似 PropertyChanging 的事件来提供此信息,无论它是否支持 e.Cancel.

I would also settle for a PropertyChanging-like event which provides this information, whether or not it supports e.Cancel.

推荐答案

如答案所示,我必须实现自己的解决方案.为了他人的利益,我已将其展示在此处:

As indicated by the answers, I had to implement my own solution. For the benefit of others, I've presented it here:

扩展的 PropertyChanged 事件

此事件经过专门设计,可向后兼容旧的 propertyChanged 事件.它可以与调用者的简单 PropertyChangedEventArgs 互换使用.当然,在这种情况下,事件处理程序有责任检查传递的 PropertyChangedEventArgs 是否可以向下转换为 PropertyChangedExtendedEventArgs,如果他们想使用它.如果他们只对 PropertyName 属性感兴趣,则不需要向下转换.

This event has been specially designed to be backwards compatible with old propertyChanged events. It can be used interchangeably with the simple PropertyChangedEventArgs by callers. Of course, in such cases, it is the responsibility of the event handler to check if the passed PropertyChangedEventArgs can be downcast to a PropertyChangedExtendedEventArgs, if they want to use it. No downcasting is necessary if all they're interested in is the PropertyName property.

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

<小时>

示例 1

用户现在可以指定更高级的 NotifyPropertyChanged 方法,该方法允许属性设置器传入其旧值:

The user can now specify a more advanced NotifyPropertyChanged method that allows property setters to pass in their old value:

public String testString
{
    get { return testString; }
    set
    {
        String temp = testString;
        testValue2 = value;
        NotifyPropertyChanged("TestString", temp, value);
    }
}

新的 NotifyPropertyChanged 方法如下所示:

Where your new NotifyPropertyChanged method looks like this:

protected void NotifyPropertyChanged<T>(string propertyName, T oldvalue, T newvalue)
{
    OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(propertyName, oldvalue, newvalue));
}

而且 OnPropertyChanged 和往常一样:

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

示例 2

或者,如果您更喜欢使用 lambda 表达式并完全取消硬编码的属性名称字符串,则可以使用以下内容:

Or if you prefer to use lambda expressions and do away with hard-coded property name strings entirely, you can use the following:

public String TestString
{
    get { return testString; }
    private set { SetNotifyingProperty(() => TestString, ref testString, value); }
}

由以下魔法支持:

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

性能

如果性能是一个问题,请参阅此问题:不使用魔法字符串实现 NotifyPropertyChanged.

If performance is a concern, see this question: Implementing NotifyPropertyChanged without magic strings.

总而言之,开销很小.添加旧值并切换到扩展事件大约会降低 15% 的速度,但仍然允许每秒 100 万个属性通知,而切换到 lambda 表达式是一个 5 倍的速度,允许每个大约 10 万个属性通知第二.这些数字远不能在任何 UI 驱动的应用程序中形成瓶颈.

In summary, the overhead is minimal. Adding the old value and switching to the extended event is about a 15% slowdown, still allowing for on the order of one million property notifications per second, and switching to lambda expressions is a 5 times slowdown allowing for approximately one hundred thousand property notifications per second. These figures are far from being able to form a bottleneck in any UI-driven application.

(可选)扩展的 PropertyChanged 接口

注意:您不必这样做.您仍然可以只实现标准的 INotifyPropertyChanged 接口.

Note: You do not have to do this. You can still just implement the standard INotifyPropertyChanged interface.

如果程序员想要创建一个要求通知属性包括旧值和新值的事件,他们需要定义和实现以下接口:

If the programmer wants to create an event that requires notifying properties to include an old value and a new value, they would need to define and implement the following interface:

// Summary: Notifies clients that a property value is changing, but includes extended event infomation
/* The following NotifyPropertyChanged Interface is employed when you wish to enforce the inclusion of old and
 * new values. (Users must provide PropertyChangedExtendedEventArgs, PropertyChangedEventArgs are disallowed.) */
public interface INotifyPropertyChangedExtended<T>
{
    event PropertyChangedExtendedEventHandler<T> PropertyChanged;
}

public delegate void PropertyChangedExtendedEventHandler<T>(object sender, PropertyChangedExtendedEventArgs<T> e);

现在任何挂钩 PropertyChanged 事件的人都需要提供上面定义的扩展参数.请注意,根据您的用例,您的 UI 可能仍需要您实现基本的 INotifyPropertyChanged 接口和事件,这会与此冲突.例如,如果您正在构建与此行为相关的自己的 UI 元素,您就会这样做.

Now anyone hooking the PropertyChanged event needs to supply the extended args defined above. Note that depending on your use case, your UI may still require you to implement the basic INotifyPropertyChanged interface and event, which would conflict with this one. This is the sort of thing you would do if, for instance, you were building your own UI elements that hinged on this behaviour.

8 年后常见问题解答 - 我该如何使用它?

以上示例展示了您将如何发送新的属性信息,但并未展示您将如何使用它们.晚了 8 年,但这是该事件的实施示例(向@Paddy 大喊,以弥补过去 6 年的不足):

The above examples show how you would send the new property information, but not how you would consume them. 8 Years late, but here's an example of an implementation of the event (shout-out to @Paddy for filling in for the deficiency the past 6 years):

myNotifyingClass.PropertyChanged += OnSomePropertyChanged;

private void OnSomePropertyChanged(object sender, PropertyChangedEventArgs e)
{
    // Without casting 'e' is a standard PropertyChanged event
    Debug.WriteLine($"'{e.PropertyName}' has changed.");

    // If you just care to check whether a certain properties changed, do so as usual
    if (e.PropertyName == nameof(SomeClass.Description))
    {
        myNotifyingClass.MarkAsDirty(); // For example
    }

    // If the old/new value are if interest, you can cast in those situations
    if (e.PropertyName == nameof(SomeClass.SortKey))
    {
        // For example, use it to order by some new property first, but by the last property second.
        if(e is PropertyChangedExtendedEventArgs<string> sortKeyChanged)
            myNotifyingClass.OrderBy(sortKeyChanged.NewValue, then_by: sortKeyChanged.OldValue);
        else
            throw new Exception("I must have forgotten to use the extended args!");
    }

    // To support more general operations, see the note below on creating interfaces
}

正如我们在上面的例子中所注意到的,如果不先进行强制转换,我们对这些泛型参数无能为力.那是因为 8 年前,我可能不知道协方差是什么.如果您希望它更有用,那么定义一些接口可能是有意义的,您可以使用这些接口在不知道运行时类型的情况下进行类型检查和提取属性值:

As we note in the above example, there's not much we can do with these generic arguments without casting first. That's because 8 years ago, I may or may not have even known what covariance was. If you would like this to be even more useful, it may make sense to define some interfaces you can use to do type checking and extract property values without knowing the runtime type:

public interface IPropertyChangedExtendedEventArgs<out T> : IPropertyChangedEventArgs
{
    public virtual T OldValue { get; }
    public virtual T NewValue { get; }
}

public class PropertyChangedExtendedEventArgs<T> : IPropertyChangedExtendedEventArgs<T>
{
    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;
    }
}

现在更好用了:

if (e is IPropertyChangedExtendedEventArgs<object> anyProperty)
    Console.WriteLine($"'{anyProperty.PropertyName}' has changed, " + 
        $"from '{anyProperty.OldValue}' to '{anyProperty.NewValue}'.");

我希望能解决问题!

这篇关于NotifyPropertyChanged 事件,其中事件参数包含旧值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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