代表,我不明白的事件约定

时间:2011-12-29 19:29:10

标签: c# events delegates

我在简短的书中从C#看了这个例子 (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附加该类中的事件并跳过该方法吗?!

3 个答案:

答案 0 :(得分:6)

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

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

yourObject.PriceChanged += someMethodWithTheAppropriateSignature;

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

在这种情况下,您的活动被声明为:

public event EventHandler<PriceChangedEventArgs> PriceChanged;

EventHandler<PriceChangedEventArgs>是一个代表,其签名为:

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);
}

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

答案 1 :(得分:4)

这是调用事件的便利。

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

因为相同的处理程序可以用于多个事件,所以传递发件人的实例是一旦它被触发就可以可靠地取消订阅的唯一方法。

我认为调用的首选方法是首先分配给变量,以免PriceChanged在检查后变为null,但在调用之前:

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

答案 2 :(得分:3)

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

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

我建议您改为使用空委托:

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

因为它允许你只写:

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

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

  

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

多个事件生成器可能使用相同的事件处理程序。发件人告诉调用的生成者。您应该始终按照预期发送正确的事件生成器,如果不是

,您将打破打开/关闭原则
  

为什么我需要这种方法?

除非你以其他方式重复代码(例如生成EventArgs类)

,否则你不会这样做