2009-12-08 38 views
2

当事件没有订阅者时,如何确保在事件触发时不会抛出异常。如何安全地触发事件

// Delegate declaration 
public delegate void _delDisplayChange(object sender,string option); 

// Event declaration 
public event _delDisplayChange DisplayChange; 

//throwing the event 
DisplayChange(this, "DISTRIBUTION"); 
+2

我前段时间发布了一个关于这个话题的问题,并且也为它提供了相当多的火焰,但它可能是澄清一些问题的好地方:http://stackoverflow.com/questions/ 840715 /正确的方式提高事件在网络框架 –

+0

感谢您的链接到问题 – Brad

+1

请注意,成功地避免例外只是你的忧虑的开始,而不是结束。如果您处于多线程状态,您还需要担心是否调用陈旧事件处理程序的*完全正交*问题,并且如果是这样,请您正确处理该情况。有关详细信息,请参阅http://blogs.msdn.com/ericlippert/archive/2009/04/29/events-and-races.aspx。 –

回答

18

这里是推荐的方式做到这一点:

protected void RaiseDisplayChanged(string message) 
{ 
    var handlers = DisplayChange; 
    if(handlers != null) 
     handlers(this, message); 
} 

复制事件处理程序枚举检查做了两件事之前:

  1. 如果DisplayChange处理程序成为检查和射击之间空,你不要死
  2. 如果侦听器在枚举DisplayChange列表的同时修改DisplayChange列表,则不会遇到奇怪现象。

此外,您没有使用标准的事件协议。您的代表应该是:

public delegate void DisplayChangeDelegate(object sender, OptionsEventArgs args); 

其中OptionsEventArgs从EventArgs派生。更进一步,在.Net 3.5中,你不应该像这样定义一个委托。相反,你应该只定义您的事件:

public event EventHandler<OptionsEventArgs> DisplayChanged; 

我喜欢通过定义这个类来把它连一步:

public class EventArgs<T> : EventArgs 
{ 
    public T Payload { get; private set } 
    public EventArgs(T payload) 
    { 
     Payload = payload; 
    } 
} 

然后,你不需要定义OptionsEventArgs:

public event EventHandler<EventArgs<string>> DisplayChanged; 

只是一些东西去思考...

+0

谢谢你的回答...和附加信息 – Brad

+0

很好的答案,但它也值得解释为什么支持字段可以成为'null'(多线程),为什么值得做事件注册并提出线程安全的开始。 –

+1

应考虑使'RaiseDisplayChanged(string)'虚拟,所以派生类可以重写事件的内部行为。 – walkingTarget

4

更改此:

// Event declaration  
public event _delDisplayChange DisplayChange; 

这样:

// Event declaration  
public event _delDisplayChange DisplayChange = delegate{}; 

这将确保您的活动将始终有至少一个用户。

+0

不需要添加一个虚拟用户,该虚拟用户占用内存并且不遵守任何逻辑;检查null与事件/订户逻辑的基本原理更加一致。 – CesarGon

+0

这是一个有趣的转折,但像塞萨尔说,有更好的方法。 –

+0

什么是“事件/订户逻辑的基本原理”,以及为什么检查null与它一致? –

1

正如布赖恩说:许多来源建议作出检查它不为空事件之前的副本:

_delDisplayChange displayChangeCopy = DisplayChange; 
if (displayChangeCopy != null) 
    displayChangeCopy(this, "DISTRIBUTION"); 

这有助于使你的代码更加线程安全的,因为displayChangeCopy的值不会空校验,并且在调用之间改变。

+2

这不会让你的代码是线程安全的。它消除了一个竞赛条件。它并没有消除更一般的竞争条件,即DisplayChange的值存储与本地副本的调用之间* DisplayChange的内容可能已经改变*,因此*您现在正在调用一个可能依赖的函数在被摧毁的状态*。不要在没有明确描述你正在缓解的线程危险的情况下声明代码是“线程安全的”。你几乎没有减轻这里所有的危险。 –

+0

同意 - 我已编辑帖子,使其更清楚。 – Anton

1

除了Jay的回答,以下是link关于使用空代表时的性能考虑事项。