在事件处理程序中创建,测试和引发公共事件的副本,以防止订阅者中的异常

时间:2009-07-28 15:53:06

标签: c# event-handling

我看到很多代码只是像这样调用事件处理程序:

if( OnyMyEvent != null)
    OnMyEvent(this,"args");

但是在C# Event Implementation Fundamentals, Best Practices and Conventions By Jeffrey Schaefer: 7) Event Raising Code上的这篇文章CodeProject中,他描述了一种引发事件的方法,以便例外订阅者不会影响提升者。我很好奇这是否是应该应用的最佳实践。

这是一个示例(未经过测试/编译),因此读者可以理解:

public delegate void MessageReceivedEventHandler(object sender, MessageEventArgs e);

class MessageEventArgs : EventArgs
{
    public string Message
    { get; set; }

    public MessageEventArgs( string message )
    { this.Message = message; }
}

class EventTriggeringClass
{
    public event MessageReceivedEventHandler MessageReceived;

    protected virtual void OnMessageReceived( MessageEventArgs e )
    {
        this.RaiseTriggerOnMessageReceived( e );
    }

    private void RaiseOnMessageReceived( MessageEventArgs e )
    {
        MessageReceivedEventHandler handler = this.MessageReceived;
        if ( handler != null )
        {
            Delegate[] eventHandlers = handler.GetInvocationList();
            foreach ( Delegate currentHandler in eventHandlers )
            {
                MessageReceivedEventHandler currentSubscriber = ( currentHandler as MessageReceivedEventHandler );
                try
                {
                    currentSubscriber( this, e );
                }
                catch ( Exception ex )
                {
                    Debug.Assert( ex == null, ex.Message, ex.ToString() );
                }
            }
        }
    }    

    public void Read()
    {
        bool foundMessage = false, hasMoreMessages = true;
        string msg;
        while( hasMoreMessages )
        {
            // this way or..
            if( foundMessage )
                this.OnMessageReceived( new MessageEventArgs( msg ) );
            // the other way
            if( MessageReceived != null )
                MessageReceived(this, new MessageEventArgs( msg ) );
        }
    }
}

2 个答案:

答案 0 :(得分:1)

防止订阅者抛出异常的方法是处理异常。排除log-and-rethrow(这不是处理异常,只是让它绕道而行),有两种异常处理:智能和愚蠢。

智能异常处理是指当您知道给定方法可以抛出什么类型的异常时,您了解可能导致该异常发生的情况,并且您知道从中恢复的正确方法是什么。

愚蠢的异常处理就是其他一切。如果您不知道为什么方法会抛出异常,您就无法知道处理异常是安全的。

如果事件处理程序有六个订阅者,如果第一个订阅者抛出异常,你怎么知道调用其他五个是安全的?你没有。你有一个例外。你完成了,直到你找到导致异常的原因并修复它。如果你调用其他事件处理程序,你可以期望的最好的是导致异常的条件不会导致它们发生故障。

在某些情况下,这是不正确的,但一般来说,如果这些情况适用,您就可以进行智能异常处理。例如,如果订阅事件处理程序的事情是向外部系统发送异常,您可能会说“如果其中一个失败,我仍然应该发送所有其他消息”。但您只知道,因为您了解事件及其处理程序正在协作解决的特定问题空间。

但是默认这样做?这与最佳做法完全相反。

答案 1 :(得分:0)

这不是最佳做法,为什么你会隐藏一个例外? 如果这是您不信任的代码的异常,则不应直接从可信代码调用此代码(事件与否)。

如果仅在发布中而不是在调试中引发异常,该怎么办?如果根据某些神秘设置仅在某些计算机上引发异常怎么办?您将很难找到错误。

必须避免

catch(异常)。请快点失败!

在事件参数中设置错误更具表现力,订阅者将知道如何通知您错误:

public class MyEventArgs : EventArgs
{
  private List<MyException> errors = new List<MyException>();

  public ICollection<MyException> Errors { get { return errors; } }
}