Могу ли я иметь сильные исключения безопасности и событий? - PullRequest
5 голосов
/ 08 мая 2009

Предположим, у меня есть метод, который изменяет состояние объекта и запускает событие, чтобы уведомить слушателей об этом изменении состояния:

public class Example
{
   public int Counter { get; private set; }

   public void IncreaseCounter()
   {
      this.Counter = this.Counter + 1;
      OnCounterChanged(EventArgs.Empty);
   }

   protected virtual void OnCounterChanged(EventArgs args)
   {
      if (CounterChanged != null)
         CounterChanged(this,args);
   }

   public event EventHandler CounterChanged;
}

Обработчики событий могут выдать исключение, даже если IncreaseCounter успешно завершил изменение состояния. Таким образом, у нас нет строгой исключения безопасности здесь:

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

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

Ответы [ 5 ]

3 голосов
/ 08 мая 2009

Чтобы исключить распространение исключения в обработчике на генератор событий, ответ состоит в том, чтобы вручную вызывать каждый элемент в делегате MultiCast (то есть обработчике события) внутри try-catch

Будут вызваны все обработчики, и исключение не будет распространено.

public EventHandler<EventArgs> SomeEvent;

protected void OnSomeEvent(EventArgs args)
{
    var handler = SomeEvent;
    if (handler != null)
    {
        foreach (EventHandler<EventArgs> item in handler.GetInvocationList())
        {
            try
            {
                item(this, args);
            }
            catch (Exception e)
            {
                // handle / report / ignore exception
            }
        }                
    }
}

Осталось вам реализовать логику того, что делать, когда один или несколько получателей события бросают, а другие нет. Функция catch () также может перехватывать определенное исключение и откатывать любые изменения, если это имеет смысл, позволяя получателю события сигнализировать источнику события о возникновении исключительной ситуации.

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

2 голосов
/ 08 мая 2009

Общий шаблон, который вы увидите для фреймворков:

public class Example
{
   public int Counter { get; private set; }

   public void IncreaseCounter()
   {
      OnCounterChanging(EventArgs.Empty);
      this.Counter = this.Counter + 1;
      OnCounterChanged(EventArgs.Empty);
   }

   protected virtual void OnCounterChanged(EventArgs args)
   {
      if (CounterChanged != null)
         CounterChanged(this, args);
   }

   protected virtual void OnCounterChanging(EventArgs args)
   {
      if (CounterChanging != null)
         CounterChanging(this, args);
   }

   public event EventHandler<EventArgs> CounterChanging;
   public event EventHandler<EventArgs> CounterChanged;
}

Если пользователь хочет выдать исключение, чтобы предотвратить изменение значения, он должен сделать это в событии OnCounterChanging () вместо OnCounterChanged (). По определению имени (прошедшее время, суффикс -ed), которое подразумевает, что значение было изменено.

Edit:

Обратите внимание, что вы обычно хотите избежать большого количества дополнительных блоков try..catch / finally, поскольку обработчики исключений (включая try..finally) дороги в зависимости от языковой реализации. то есть модель фрейма стека win32 или модель исключений, отображаемых на ПК, имеют свои плюсы и минусы, однако, если их слишком много, они оба будут дорогостоящими (либо в пространстве, либо в скорости выполнения). Еще одна вещь, которую нужно иметь в виду при создании фреймворка.

1 голос
/ 08 мая 2009

Ну, вы могли бы компенсировать, если это было важно:

public void IncreaseCounter()
{
  int oldCounter = this.Counter;
  try {
      this.Counter = this.Counter + 1;
      OnCounterChanged(EventArgs.Empty);
  } catch {
      this.Counter = oldCounter;
      throw;
  }
}

Очевидно, что было бы лучше, если бы вы могли разговаривать с полем (поскольку назначение поля не будет ошибкой). Вы могли бы также обернуть это в болельщик:

void SetField<T>(ref T field, T value, EventHandler handler) {
    T oldValue = field;
    try {
         field = value;
         if(handler != null) handler(this, EventArgs.Empty);
    } catch {
         field = oldField;
         throw;
    }
}

и звоните:

SetField(ref counter, counter + 1, CounterChanged);

или что-то подобное ...

0 голосов
/ 08 мая 2009

В этом случае, я думаю, вы должны взять реплику от Workflow Foundation. В методе OnCounterChanged окружите вызов делегата универсальным блоком try-catch. В обработчике улова вам нужно выполнить то, что эффективно компенсирует действие - откатить счетчик.

0 голосов
/ 08 мая 2009

Не приведет ли простое изменение порядка двух строк в IncreaseCounter к вашей строгой исключительной безопасности?

Или, если вам нужно , чтобы увеличить Counter перед тем, как поднять событие:

public void IncreaseCounter()
{
    this.Counter += 1;

    try
    {
        OnCounterChanged(EventArgs.Empty);
    }
    catch
    {
        this.Counter -= 1;

        throw;
    }
}

В ситуации, когда у вас происходят более сложные изменения состояния (побочные эффекты), тогда это становится довольно сложным (например, вам может понадобиться клонировать определенные объекты), но в этом примере это будет казаться довольно простым. 1010 *

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...