2011-12-29 102 views
3

我在书中简而言之 (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!"); 
    } 
} 

我不明白,这就是为什么使用这个惯例看这个例子从C# ..

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

为什么我需要这种方法,为什么我会关心给它“this”参数?!?不能,我只是将该类中的事件与测试类中的方法PriceChanged直接相连并跳过该方法?!?

+0

你怎么会重视吗?该方法正在附加。您不必使用该方法,只需使用其中的代码,即可代替所需的位置。 – 2011-12-29 19:34:17

回答

3

null检查,因为一个(事件)委托列表不如果没有订户,则为空,但是null

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

我建议你使用,而不是一个空的委托:

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

因为它可以让你只写:

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

这是线程安全的代码更易于阅读。

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

相同的事件处理程序可能由多个事件发电机使用。发件人告诉哪个生成调用。你应该总是发送正确的事件发生器,因为它是预期的,你会打破/关闭原则,如果你不打

为什么我需要该方法?

你不这样做,除非你本来的重复代码(如生成EventArgs类)

+0

好的建议谢谢 – 2011-12-29 19:44:03

+0

@DmitryMakovetskiyd:读我的更新。 – jgauffin 2011-12-29 19:47:06

6

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

此方法用于提高的事件,而不是订阅它。您可以从另一个类容易订阅事件:

yourObject.PriceChanged += someMethodWithTheAppropriateSignature; 

但是,如果你想拥有的事件“火”,类需要提高的事件。 “this”参数在EventHandler<T>中提供sender参数。按照惯例,用于事件的代表有两个参数,第一个是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); 
} 

这可以保护你在多线程的情况,因为它可能是一个单一的订阅实际上可能你的空检查和培养之间退订,如果你有多个线程运行。

+0

你的代码不是线程安全的。任何使用托管资源(或任何其他有状态资源)的类可能已经放置在您的列表副本和实际调用之间。示例(1. listcopy,2.取消订阅/处置,3.调用) – jgauffin 2011-12-29 19:40:36

+0

是否存在该约定的原因...为什么我需要传递发件人参数? – 2011-12-29 19:42:42

+0

@DmitryMakovetskiyd:多个事件生成器可能会使用相同的事件处理程序。发件人告诉哪个生成调用。您应该始终按照预期发送正确的事件生成器,如果不这样做,您将打破开放/关闭原则。 – jgauffin 2011-12-29 19:44:34

4

这是调用该事件的便利。

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

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

我想调用的首选方法是分配给一个变量第一,以免PriceChanged成为检查后无效,但在调用之前:使用

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