Создайте, протестируйте и создайте копию общедоступного события в обработчике событий для защиты от исключений в подписчиках.

Я вижу много кода, который просто вызывает обработчик событий следующим образом:

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

Но в этой статье Джеффри Шефера Основы реализации событий C#, лучшие практики и соглашения : 7) Код создания событий в 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 ) );
        }
    }
}

person CS.    schedule 28.07.2009    source источник


Ответы (2)


Способ защиты от исключений, создаваемых подписчиками, заключается в обработке исключений. За исключением log-and-rethrow (который не столько обрабатывает исключение, сколько позволяет ему совершить небольшой обход по пути), существует два вида обработки исключений: умная и глупая.

Интеллектуальная обработка исключений — это когда вы знаете, какое исключение может сгенерировать данный метод, понимаете обстоятельства, которые могут привести к этому, и знаете, как правильно исправить ситуацию.

Глупая обработка исключений — это все остальное. Если вы не знаете, почему метод генерирует исключение, вы не можете знать, безопасно ли обрабатывать исключение.

В случае обработчика событий с шестью подписчиками, если первый подписчик выдает исключение, как узнать, безопасно ли вызывать остальные пять? Вы не знаете. Вы получили исключение. Все готово, пока вы не выясните, что вызвало исключение, и не исправите его. Лучшее, на что вы можете надеяться, вызывая другие обработчики событий, это то, что условие, вызвавшее исключение, не приведет к их сбою.

Существуют обстоятельства, при которых это не так, но, вообще говоря, если эти обстоятельства применимы, вы можете выполнить разумную обработку исключений. Например, если вещи, подписавшиеся на обработчик событий, отправляют исключения во внешние системы, вы можете сказать: «Если одна из этих вещей выйдет из строя, я все равно должен отправить все остальные сообщения». Но вы знаете это только потому, что знаете что-то о конкретном проблемном пространстве, над решением которого совместно работают событие и его обработчики.

Но делать это по умолчанию? Это полная противоположность лучшей практике.

person Robert Rossney    schedule 28.07.2009
comment
Просто чтобы быть точным, причина, по которой я привожу передовой опыт, заключается в том, что в самом верху упомянутой статьи говорится: Эта статья представляет основы реализации событий, **рекомендации, и условности.**. В любом случае. Если я вас правильно понимаю, вы говорите: доверяйте подписчикам, если они терпят неудачу - это не влияет на обработчик событий, потому что их (глупая|отсутствие) обработка исключений в их контексте вызвала их потерпеть неудачу, независимо от того, для какой цели был предназначен подписчик. - person CS.; 29.07.2009
comment
Вот и все. Если, конечно, вы достаточно знаете о своих подписчиках, чтобы знать, что вы можете просто выстрелить неудавшемуся подписчику в голову и продолжать двигаться дальше. - person Robert Rossney; 29.07.2009

Это не лучшая практика, с какой стати вам скрывать исключение? Если это исключение из кода, которому вы не доверяете, этот код не следует вызывать (событие или нет) непосредственно из надежного кода.

Что, если исключение возникает только в релизе, а не в отладке? Что, если исключение возникает только на каком-то компьютере в зависимости от каких-то загадочных настроек? Вам будет трудно найти ошибку.

catch (исключение) следует избегать. Пожалуйста, терпите неудачу быстро!

Установка ошибки в аргументах события более выразительна, подписчик будет знать, как уведомить вас об ошибке:

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

  public ICollection<MyException> Errors { get { return errors; } }
}
person Guillaume    schedule 28.07.2009
comment
Во-первых, я согласен. Чтобы уточнить, вопрос, вероятно, таков: разрешить всем подписчикам получать событие, или пусть первые 2 получают его, 3-й подписчик бросает, остальные 6 никогда не уведомляются. Лучше всего требовать, чтобы все подписчики были уведомлены, даже если один или несколько подписчиков бросают. - person CS.; 28.07.2009
comment
Ну, вы можете сделать это с конкретным исключением, но я настоятельно рекомендую вам установить ошибку в ваших EventArgs и избегать этого странного вызова события. - person Guillaume; 29.07.2009