如何在 C# 中 PropertyChanged 事件的 INotifyPropertyChanged 实现中捕获旧值和新值

Posted

技术标签:

【中文标题】如何在 C# 中 PropertyChanged 事件的 INotifyPropertyChanged 实现中捕获旧值和新值【英文标题】:How to capture old value and new value in INotifyPropertyChanged implementation of PropertyChanged event in C# 【发布时间】:2018-05-23 06:16:15 【问题描述】:

我需要在 PropertyChanged 事件处理程序中捕获旧值、新值和属性名称。 INotifyPropertyChanged 的​​默认实现仅提供属性的名称。所以在互联网上搜索后,我在这里发现了类似的问题,属性的扩展实现发生了变化。

下面是那个 Q 和接口声明的链接:

NotifyPropertyChanged event where event args contain the old value

public interface INotifyPropertyChangedExtended<T>

    event PropertyChangedExtendedEventHandler<T> PropertyChanged;

公共委托 void PropertyChangedExtendedEventHandler(object sender, PropertyChangedExtendedEventArgs e);

上面的接口可以解决我的问题,但是我不明白如何在我的实体类上实现通用接口,因为 T 会根据属性的数据类型而改变。

谁能帮我理解一下,有没有可能用T参数实现这个接口。示例实现将非常有帮助。

提前感谢您的帮助。

乌梅什

编辑#1: 根据 Peter 的回复,发布更新的代码可能对想要在 PropertyChanged 事件中捕获新旧值的人有用。

    public class PropertyChangedExtendedEventArgs : PropertyChangedEventArgs

    public string OldValue  get; set; 

    public string NewValue  get; set; 

    public PropertyChangedExtendedEventArgs(string propertyName, string oldValue, string newValue) : base(propertyName)
    
        OldValue = oldValue;
        NewValue = newValue;
    


    //
// Summary:
//     Notifies clients that a property value has changed.
public interface INotifyPropertyChangedEnhanced

    //
    // Summary:
    //     Occurs when a property value changes.
    event PropertyChangedEventHandlerEnhanced PropertyChanged;


public delegate void PropertyChangedEventHandlerEnhanced(object sender, PropertyChangedExtendedEventArgs e);

public abstract class BindableBase : INotifyPropertyChangedEnhanced

    public event PropertyChangedEventHandlerEnhanced PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] 
    string propertyName = null)
    
        if (Equals(storage, value))
        
            return false;
        

        var oldValue = storage;
        storage = value;
        this.OnPropertyChanged(oldValue, value, propertyName);
        return true;
    
    protected void OnPropertyChanged<T>(T oldValue, T newValue, [CallerMemberName] string propertyName = null)
    
        this.PropertyChanged?.Invoke(this, new PropertyChangedExtendedEventArgs(propertyName, oldValue?.ToString(), newValue?.ToString()));
    

【问题讨论】:

【参考方案1】:

只需实现您自己的 PropertyChangedEventArgs。

不需要实现新的接口。

此解决方案有效。这是多态性的好处之一

public class PropertyChangedExtendedEventArgs : PropertyChangedEventArgs

        public virtual object OldValue  get; private set; 
        public virtual object NewValue  get; private set; 

        public PropertyChangedExtendedEventArgs(string propertyName, object oldValue, 
               object newValue)
               : base(propertyName)
        
            OldValue = oldValue;
            NewValue = newValue;
        
    

public class NotifyObject : INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName, object oldvalue, object newvalue)
        
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedExtendedEventArgs(propertyName, oldvalue, newvalue));
        
    



public class Person : NotifyObject
    
        private int _name;
        public int Name
        
            get  return _name; 
            set
            
                var oldValue = _name;
                _name = value;
                NotifyPropertyChanged("Name", oldValue, value);

            
        
    

还有你的视图模型

Person person = new Person();
person.PropertyChanged += person_PropertyChanged;

  void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
        
            var args = e as PropertyChangedExtendedEventArgs;
            //do whatever you want
        

【讨论】:

【参考方案2】:

我不明白如何在我的实体类上实现通用接口,因为 T 会根据属性的数据类型而改变

没错。您正在查看的答案实际上并不是一个很好的答案,因为它包含无法编译或没有意义的代码。

通过传递继承非泛型 PropertyChangedEventArgs 类的泛型 args 类来引发 非泛型 PropertyChanged 事件的基本思想本身是可以的。在您的情况下应该可以正常工作。

但是!您将无法实现 answer 建议的通用接口,因为(正如您已经注意到的)当属性具有不同类型时,该接口不起作用。实际上,您正在查看的答案甚至不会使用该接口进行编译,因为它们的事件引发方法本身是通用的,并且会引发非通用 PropertyChanged 事件(该方法与通用接口不兼容) ,并且他们不会费心去实际实现他们的泛型接口(如果他们尝试过,他们会注意到泛型方法不适用于泛型接口……方法的类型参数T 与接口的不同类型参数T)。

很遗憾,高票数的答案是这样写的。它有一些有用的想法,但也有完全不可行的想法,并且只会混淆正在寻找类似答案的人(即最想阅读和使用答案的人)。

【讨论】:

谢谢彼得。我没有使用 T,而是通过对旧值和新值使用字符串类型来解决它;至于审计,我需要捕获每个属性的新旧值并将其存储在数据库中以用于报告目的。【参考方案3】:

你不能。也实现 INotifyPropetyChanging。这将提供对旧值的访问。您可以在文档中找到这两个接口的示例实现。

【讨论】:

【参考方案4】:

正如@Andy 提到的,你可以实现INotifyPropetyChanging 来获取旧值,但是存储很麻烦,所以我做了一个帮助类NotifyPropertyChangingCache 你可以使用如下:

class EntityBase : INotifyPropertyChanged, INotifyPropertyChanging
 /* ... */ 

// initiating the entity
var entity = new EntityBase();

// initiating the cache:
var notifyPropertyChangingCache = new Ice1e0.ComponentModel.NotifyPropertyChangingCache();
entity.PropertyChanging += notifyPropertyChangingCache.OnPropertyChanging;

// your property changed event looks like this
entity.PropertyChanged += (s, e) =>

    var oldValue = notifyPropertyChangingCache.GetLastPropertyValue(e.PropertyName);

    // your code goes here ...
;

// when you are done with your entity, don't forget to remove the events (we don't want to create a memory leak ...)
entity.PropertyChanging -= notifyPropertyChangingCache.OnPropertyChanging;
//entity.PropertyChanged -= ... (I assume you use not a in-line method here as I did in this example)

【讨论】:

以上是关于如何在 C# 中 PropertyChanged 事件的 INotifyPropertyChanged 实现中捕获旧值和新值的主要内容,如果未能解决你的问题,请参考以下文章

带有复选框的 C# WPF 目录树视图:检查构建项目失败,PropertyChanged 为空

C# WPF中设置绑定时,INotifyPropertyChanged接口如何对数组使用

如何在 DependencyProperty 中传播 PropertyChanged 更改

C# WPF 数据绑定

如何在不使用字符串名称的情况下引发 PropertyChanged 事件

使用PropertyChanged委托提出多个PropertyChanged事件的问题