【问题标题】:delegate, event convention that I don't understand代表,我不明白的事件约定
【发布时间】:2017-01-29 00:29:47
【问题描述】:

我从C# in nutshell book 中查看了这个示例 (http://www.albahari.com/nutshell/ch04.aspx)

using System;

public class PriceChangedEventArgs : EventArgs
{
  public readonly decimal LastPrice;
  public readonly decimal NewPrice;

  public PriceChangedEventArgs (decimal lastPrice, decimal newPrice)
  {
    LastPrice = lastPrice; NewPrice = newPrice;
  }
}

public class Stock
{
  string symbol;
  decimal price;

  public Stock (string symbol) {this.symbol = symbol;}

  public event EventHandler<PriceChangedEventArgs> PriceChanged;

  ****protected virtual void OnPriceChanged (PriceChangedEventArgs e)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }****

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      OnPriceChanged (new PriceChangedEventArgs (price, value));
      price = value;
    }  
  }
}

class Test
{
  static void Main()
  {
    Stock stock = new Stock ("THPW");
    stock.Price = 27.10M;
    // register with the PriceChanged event    
    stock.PriceChanged += stock_PriceChanged;
    stock.Price = 31.59M;
  }

  static void stock_PriceChanged (object sender, PriceChangedEventArgs e)
  {
    if ((e.NewPrice - e.LastPrice) / e.LastPrice > 0.1M)
      Console.WriteLine ("Alert, 10% stock price increase!");
  }
}

我不明白,为什么要使用这个约定......

  ****protected virtual void OnPriceChanged (PriceChangedEventArgs e)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }****

为什么我需要那个方法,为什么我要给它“this”参数?!?我不能直接在测试类中使用 PriceChanged 方法附加该类的事件并跳过该方法吗?!?

【问题讨论】:

  • 你将如何附加它?该方法正在执行附加。您不必使用该方法,您可以使用其中的代码,代替您需要的地方。

标签: c# events delegates


【解决方案1】:

您需要进行空值检查,因为在有人订阅之前,事件将为空值。如果直接 raise 并且为 null,则会抛出异常。

此方法用于引发事件,而不是订阅它。您可以轻松地从其他类订阅事件:

yourObject.PriceChanged += someMethodWithTheAppropriateSignature;

但是,当您想让事件“触发”时,该类需要引发该事件。 “this”参数在EventHandler&lt;T&gt; 中提供sender 参数。按照惯例,用于事件的委托有两个参数,第一个是object sender,它应该是引发事件的对象。第二个是EventArgsEventArgs 的子类,它提供特定于该事件的信息。该方法用于正确检查 null 并使用适当的信息引发事件。

在这种情况下,您的事件被声明为:

public event EventHandler<PriceChangedEventArgs> PriceChanged;

EventHandler&lt;PriceChangedEventArgs&gt; 是一个具有以下签名的委托:

public delegate void EventHandler<T>(object sender, T args) where T : EventArgs

这意味着必须使用两个参数引发事件 - 一个对象(发送者或“this”)和一个 PriceChangedEventArgs 的实例。

话虽如此,这个约定实际上并不是举办活动的“最佳”方式。实际上使用起来会更好:

protected virtual void OnPriceChanged (PriceChangedEventArgs e)
{
    var eventHandler = this.PriceChanged;
    if (eventHandler != null) 
        eventHandler(this, e);
}

这可以在多线程场景中保护您,因为如果您有多个线程在运行,则单个订阅可能会在您的 null 检查和 raise 之间实际取消订阅。

【讨论】:

  • 您的代码不是线程安全的。任何使用托管资源(或任何其他有状态资源)的类都可能已在您的列表副本和实际调用之间进行了处理。示例(1. listcopy,2. unsubscribe/dispose,3. Invocation)
  • 这种约定是否有原因...为什么我需要传递 sender 参数?
  • @DmitryMakovetskiyd:多个事件生成器可能使用相同的事件处理程序。发送者告诉调用是针对哪个生成的。您应该始终按预期发送正确的事件生成器,否则您将破坏打开/关闭原则。
  • @jgauffin 我在这个答案的最后特别提到了线程安全......
  • @jgauffin 制作副本时,您正在制作调用列表的完整副本。取消订阅不会影响本地副本。该代码是处理线程安全的建议的正确方法。 (请注意,我使用的是 copy 来引发事件,而不是原来的。)
【解决方案2】:

这是调用事件的方便。

您确实需要检查该事件是否有订阅者,并且通常将this 作为事件的发送者传递。

因为同一个处理程序可用于多个事件,所以传递发送者的实例是在事件触发后可靠地取消订阅事件的唯一方法。

我认为调用的首选方式是先赋值给一个变量,以免PriceChanged在检查后但在调用之前变为空:

var handler = PriceChanged;
if(handler != null) handler(this, e);

【讨论】:

    【解决方案3】:

    使用空值检查,因为(事件)委托列表不为空,但如果没有订阅者,则为 null

    但是,它不是线程安全的。因此,如果您开始使用 BackgroundWorker 或任何其他多线程技术,它可能会在您的脸上炸开。

    我建议您改用空委托:

    public event EventHandler<PriceChangedEventArgs> PriceChanged = delegate {};
    

    因为它允许你只写:

    protected virtual void OnPriceChanged (PriceChangedEventArgs e)
    {
       PriceChanged (this, e);
    }
    

    它是线程安全的,代码更容易阅读。

    为什么我要给它“this”参数?!?

    多个事件生成器可能使用相同的事件处理程序。发送者告诉调用是针对哪个生成的。您应该始终按预期发送正确的事件生成器,如果不这样做,您将打破打开/关闭原则

    为什么我需要那个方法?

    你不需要,除非你会重复代码(例如生成EventArgs类)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多