C#INotifyPropertyChanged处理程序

mFe*_*ein 2 c# wpf events mvvm

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

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

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

为什么要费心声明这个handler变量呢?它只是让我觉得不必要的复杂代码,但我甚至可以在微软自己的教程中看到这一点,为什么不把它用作:

if (this.PropertyChanged != null)
{
    var e = new PropertyChangedEventArgs(propertyName);
    this.PropertyChanged(this, e);
}
Run Code Online (Sandbox Code Playgroud)

Bra*_*NET 9

存储PropertyChanged事件是为了线程安全.事实上,你应该在技术上对所有事件这样做.

赋值创建事件及其处理程序的副本(不是引用,这将是无用的),这意味着您可以避免事件处理程序null在通过null检查后设置为正确的情况.这避免了可能引发NullReferenceException的竞争条件.

实际上,如果有的话,UI不会经常将该属性设置为null.但是为了安全起见并使用良好实践,您应该关闭处理程序.


McG*_*gle 6

我只想为BradleyDotNET的答案添加几点.

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

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

实际上,另一种模式被如此广泛地使用,以至于微软很可能永远不会对编译器进行更改,从而打破这么多应用程序.


另一点是,您实际上可以为事件添加一个空的虚拟委托,如下所示:

public event PropertyChangedEventHandler PropertyChanged = delegate { };
Run Code Online (Sandbox Code Playgroud)

这意味着您不必担心任何空检查,因为事件至少有一个附加的处理程序.但是,这种做法存在争议,因为它似乎会导致性能损失(理论上,您会为每个事件调用添加额外的调用).我个人避免使用它,因为我宁愿遭受比膨胀的运行时性能更臃肿的代码.


Eric Lippert关于这个主题的博客文章很好读.