C# INotifyPropertyChanged 处理程序

C# INotifyPropertyChanged handler

我开始阅读有关 MVVM 的内容,我经常看到的一种模式是:

public event PropertyChangedEventHandler PropertyChanged;
//.....

PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
    var e = new PropertyChangedEventArgs(propertyName);
    handler(this, e);
}

为什么要声明这个 handler 变量?在我看来,它只是不必要地使代码复杂化,但即使在 Microsoft 自己的教程中我也能看到这一点,为什么不直接将其用作:

if (this.PropertyChanged != null)
{
    var e = new PropertyChangedEventArgs(propertyName);
    this.PropertyChanged(this, e);
}

关闭 PropertyChanged 事件是为了线程安全。事实上,从技术上讲,您应该对 所有 您的事件执行此操作。

赋值创建了事件及其处理程序的副本(不是引用,这没有用),这意味着您可以避免事件处理程序在通过后立即设置为 null 的情况null 检查。这避免了会引发 NullReferenceException 的潜在竞争条件。

实际上,UI 并没有经常将 属性 设置为 null,如果有的话。但是,为了安全起见并采用良好做法,您应该关闭处理程序。

我只想对 BradleyDotNET 的回答补充几点。

Jeffrey Richter 在他的 CLR via C# 一书中指出,该模式仍然不能保证线程安全,因为编译器可以优化掉局部变量(尽管当前版本的编译器没有做到这一点优化)。他建议使用 Volatile.Read 在技术上是正确的:

PropertyChangedEventHandler handler = Volatile.Read(ref PropertyChanged);
if (handler != null)
{
    var e = new PropertyChangedEventArgs(propertyName);
    handler(this, e);
}

但实际上,另一种模式的使用如此广泛,以至于 Microsoft 很可能永远不会对编译器做出会破坏如此多应用程序的更改。


还有一点,您实际上可以 add an empty dummy delegate 参加您的活动,如下所示:

public event PropertyChangedEventHandler PropertyChanged = delegate { };

这意味着您不必担心任何空值检查,因为该事件至少附加了一个处理程序作为开始。不过,这种做法是有争议的,因为它似乎会导致性能下降(理论上,您会向每个事件调用添加额外的调用)。我个人避免使用它,因为我宁愿忍受臃肿的代码也不愿忍受臃肿的运行时性能。


This blog post by Eric Lippert 这个话题很好读。