为什么我们应该使用temprary对象来引发事件?

时间:2022-09-11 10:40:18

Most of the time when we use MVVM we use the INotifyPropertyChanged interface to provide the notification to the bindings, and the general implementation looks like this:

大多数情况下,当我们使用MVVM时,我们使用INotifyPropertyChanged接口向绑定提供通知,一般实现如下所示:

public class MyClass : INotifyPropertyChanged
{
    // properties implementation with RaisePropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

This works fine for me whenever I read the code from the experts - they wrote similar code:

每当我从专家那里读到代码时,这对我来说都很好 - 他们写了类似的代码:

public class MyClass : INotifyPropertyChanged
{
    // properties implementation with RaisePropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
        var tempchanged = PropertyChanged;
        if (tempchanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

I would like to know what is the exact reason behind creating a temporary object for the PropertyChanged event.

我想知道为PropertyChanged事件创建临时对象的确切原因是什么。

Is it only a good practice or are there any other benefits associated with it?

这只是一种好的做法,还是有任何与之相关的其他好处?

I have found the answer with Jon's answer and the explained example at:

我找到了Jon的回答和解释的例子:

Understanding C#: Raising events using a temporary variable

了解C#:使用临时变量引发事件

Here is the sample code to understand this:

以下是了解此问题的示例代码:

using System;
using System.Collections.Generic;
using System.Threading;

class Plane
{
     public event EventHandler Land;

     protected void OnLand()
     {
          if (null != Land)
          {
               Land(this, null);
           }
      }

     public void LandThePlane()
     {
          OnLand();
      }
}

class Program
{
     static void Main(string[] args)
     {
          Plane p = new Plane();
          ParameterizedThreadStart start = new ParameterizedThreadStart(Run);
          Thread thread = new Thread(start);
          thread.Start(p);

          while (true)
          {
               p.LandThePlane();
           }
      }

     static void Run(object o)
     {
          Plane p = o as Plane;
          while (p != null)
          {
               p.Land += p_Land;
               p.Land -= p_Land;
           }
      }

     static void p_Land(object sender, EventArgs e)
     {
          return;
      }
}

4 个解决方案

#1


25  

You're not creating a temporary object. You're using a local variable to avoid a race condition.

你不是在创建一个临时对象。您正在使用局部变量来避免竞争条件。

In this code:

在这段代码中:

if (PropertyChanged != null)
{
    PropertyChanged(...);
}

it's possible for PropertyChanged to become null (due to the last subscriber unsubscribing) after the nullity check, which will mean you get a NullReferenceException.

在无效检查之后,PropertyChanged可能变为空(由于最后一个订阅者取消订阅),这意味着您将获得NullReferenceException。

When you use a local variable, you ensure that the reference you check for nullity is the same reference that you use to raise the event - so you won't get the exception. There's still a race condition in that you may end up calling subscribers who have just unsubscribed, but that's unavoidable.

使用局部变量时,确保检查无效的引用与用于引发事件的引用相同 - 因此您不会获得异常。还有一个竞争条件,你最终可能会打电话给那些刚刚取消订阅的订阅者,但这是不可避免的。

#2


4  

It's to avoid the rare case when the last (or only) event handler is removed from the event between the time you check for null (to see if any event handler is attached) and the time you call the event. If that happens you would get a NullReferenceException.

这是为了避免在您检查null(查看是否附加了任何事件处理程序)和调用事件的时间之间从事件中删除最后一个(或唯一)事件处理程序时的罕见情况。如果发生这种情况,您将获得NullReferenceException。

If you're concerned about memory leaks - don't be - it's just a reference, not a copy of the event handler.

如果你担心内存泄漏 - 不要 - 它只是一个引用,而不是事件处理程序的副本。

More details can be found here

更多详情可在这找到

#3


1  

This is good practise for reasons of thread safety.

出于螺纹安全的原因,这是一种很好的做法。

In your original code, it is theoretically possible for a separate thread to remove the PropertyChanged handler after the if statement but before the event is raised on the following line. This would cause a NullReferenceException.

在原始代码中,理论上可以让单独的线程在if语句之后但在下一行引发事件之前删除PropertyChanged处理程序。这会导致NullReferenceException。

The second sample removes this risk.

第二个样本消除了这种风险。

#4


0  

It only makes a difference when dealing with multi-threaded scenarios: When the last event handler is deregistered after the != null check, the actual call might run into a NullReferenceException in code 1.

它只在处理多线程场景时有所不同:当在!= null检查之后取消注册最后一个事件处理程序时,实际调用可能会在代码1中遇到NullReferenceException。

Code 2, however, doesn't have this problem as Delegates (the concept behind events) are immutable and thus the value of the temporary variable can't change.

但是,代码2没有这个问题,因为Delegates(事件背后的概念)是不可变的,因此临时变量的值不能改变。

However, I would recommend always going with variant 2 as a best practice - this might save you a headache in the future ;-)

但是,我建议总是将变体2作为最佳实践 - 这可能会让你在将来头疼;-)

#1


25  

You're not creating a temporary object. You're using a local variable to avoid a race condition.

你不是在创建一个临时对象。您正在使用局部变量来避免竞争条件。

In this code:

在这段代码中:

if (PropertyChanged != null)
{
    PropertyChanged(...);
}

it's possible for PropertyChanged to become null (due to the last subscriber unsubscribing) after the nullity check, which will mean you get a NullReferenceException.

在无效检查之后,PropertyChanged可能变为空(由于最后一个订阅者取消订阅),这意味着您将获得NullReferenceException。

When you use a local variable, you ensure that the reference you check for nullity is the same reference that you use to raise the event - so you won't get the exception. There's still a race condition in that you may end up calling subscribers who have just unsubscribed, but that's unavoidable.

使用局部变量时,确保检查无效的引用与用于引发事件的引用相同 - 因此您不会获得异常。还有一个竞争条件,你最终可能会打电话给那些刚刚取消订阅的订阅者,但这是不可避免的。

#2


4  

It's to avoid the rare case when the last (or only) event handler is removed from the event between the time you check for null (to see if any event handler is attached) and the time you call the event. If that happens you would get a NullReferenceException.

这是为了避免在您检查null(查看是否附加了任何事件处理程序)和调用事件的时间之间从事件中删除最后一个(或唯一)事件处理程序时的罕见情况。如果发生这种情况,您将获得NullReferenceException。

If you're concerned about memory leaks - don't be - it's just a reference, not a copy of the event handler.

如果你担心内存泄漏 - 不要 - 它只是一个引用,而不是事件处理程序的副本。

More details can be found here

更多详情可在这找到

#3


1  

This is good practise for reasons of thread safety.

出于螺纹安全的原因,这是一种很好的做法。

In your original code, it is theoretically possible for a separate thread to remove the PropertyChanged handler after the if statement but before the event is raised on the following line. This would cause a NullReferenceException.

在原始代码中,理论上可以让单独的线程在if语句之后但在下一行引发事件之前删除PropertyChanged处理程序。这会导致NullReferenceException。

The second sample removes this risk.

第二个样本消除了这种风险。

#4


0  

It only makes a difference when dealing with multi-threaded scenarios: When the last event handler is deregistered after the != null check, the actual call might run into a NullReferenceException in code 1.

它只在处理多线程场景时有所不同:当在!= null检查之后取消注册最后一个事件处理程序时,实际调用可能会在代码1中遇到NullReferenceException。

Code 2, however, doesn't have this problem as Delegates (the concept behind events) are immutable and thus the value of the temporary variable can't change.

但是,代码2没有这个问题,因为Delegates(事件背后的概念)是不可变的,因此临时变量的值不能改变。

However, I would recommend always going with variant 2 as a best practice - this might save you a headache in the future ;-)

但是,我建议总是将变体2作为最佳实践 - 这可能会让你在将来头疼;-)