Может ли блок области действия с ключевым словом «using» реагировать на исключения? - PullRequest
3 голосов
/ 16 ноября 2009

У меня есть необходимость сделать некоторые записи в моем коде. Мне нужно использовать внутреннюю библиотеку, разработанную компанией, для записи некоторой информации. Вот как это работает.

Recorder recorder = Recorder.StartTiming();
DoSomeWork();
recorder.Stop();  // Writes some diagnostic information.

Чтобы гарантировать, что Stop () всегда вызывается, я создал класс-обертку, который позволяет чистый блок "using".

using (RecorderWrapper recorderWrapper = new RecorderWrapper)  // Automatically calls Recorder.StartTiming() under the covers
{
   DoSomeWork();
}  // When the recorderWrapper goes out of scope, the 'using' statement calls recorderWrapper.Dispose() automatically - which calls recorder.Stop() under the covers

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

Recorder recorder = Recorder.StartTiming();
try
{
   DoSomeWork();
}
catch (Exception ex)
{
   recorder.ReportFailure(ex);  // Write out some exception details associated with this "transaction"
}
recorder.Stop();  // Writes some diagnostic information.

Я бы хотел избежать попыток / ловит все мои блоки использования с помощью RecorderWrapper. Есть ли способ, которым я могу выполнить вызов "ReportFailure ()" и при этом использовать блок области действия "using"?

В частности, я хочу, чтобы все в моей команде «впали в пропасть успеха», то есть чтобы было легко делать правильные вещи. Для меня это означает, что очень трудно забыть вызвать устройство записи. Остановить () или забыть попробовать / поймать.

Спасибо!

Ответы [ 8 ]

7 голосов
/ 16 ноября 2009

Возможно, вы сможете создать метод на рекордере, чтобы скрыть это:

public void Record(Action act)
{
    try
    {
        this.StartTiming();
        act();
    }
    catch(Exception ex)
    {
        this.ReportFailure(ex);
    }
    finally
    {
        this.Stop();
    }
}

Итак, ваш пример будет просто:

recorder.Record(DoSomeWork);
3 голосов
/ 16 ноября 2009

Вы можете продолжать использовать RecorderWrapper, который у вас есть, но добавьте метод TryExecuting, который принимает лямбда-выражения того, что вы хотите сделать, и добавьте его в блок try / catch. например:

using (RecorderWrapper recorderWrapper = new RecorderWrapper)  // Automatically calls Recorder.StartTiming() under the covers
{
    recorderWrapper.TryExecuting(() => DoSomeWork());
}

Внутри RecorderWrapper:

public void TryExecuting(Action work)
{
    try { work(); }
    catch(Exception ex) { this.ReportFailure(ex); }
}
3 голосов
/ 16 ноября 2009

Вы всегда можете попробовать что-то вроде:

Редактирование 280Z28: я использую статический метод StartNew(), подобный Stopwatch.StartNew(). Сделайте свой Recorder класс IDisposable и позвоните Stop() с Dispose(). Я не думаю, что это становится более ясным, чем это.

using (Recorder recorder = Recorder.StartNew())
{
    try
    {
        DoSomeWork();
    }
    catch (Exception ex)
    {
        recorder.ReportFailure(ex);
    }
}
1 голос
/ 16 ноября 2009

Упс, я не заметил, что StartTiming создает новый экземпляр Recorder. Я обновил код, чтобы учесть это. Теперь функция Wrap больше не принимает параметр Recorder, а вместо этого передает созданный рекордер в качестве аргумента делегату действия, переданному вызывающей стороной, чтобы вызывающий мог использовать его при необходимости.

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

Сначала определите класс для выполнения упаковки:

public static class RecorderScope
{
   public static void Wrap(Action<Recorder> action)
   {
      Recorder recorder = Recorder.StartTiming();
      try
      {
         action(recorder);
      }
      catch(Exception exception)
      {
         recorder.ReportFailure(exception);
      }
      finally
      {
         recorder.Stop();
      }
   }
}

Теперь используйте так:

RecorderScope.Wrap(
   (recorder) =>
   {
      // note, the recorder is passed in here so you can use it if needed -
      // if you never need it you can remove it from the Wrap function.
      DoSomeWork();
   });

Хотя один вопрос - действительно ли желательно, чтобы обработчик перехвата глотал исключение, не перебрасывая его? Обычно это плохая практика.

Кстати, я добавлю к этому шаблону, который может быть полезен. Хотя это не похоже на то, что относится к тому, что вы делаете в этом случае: вы когда-нибудь хотели сделать что-то подобное выше, где вы хотите обернуть некоторый код набором действий при запуске и действиями по завершению, но вы также должны быть возможность кодировать какой-то конкретный код обработки исключений. Что ж, если вы измените функцию Wrap, чтобы она также принимала делегат Action и ограничивало T до Exception, тогда у вас есть оболочка, которая позволяет пользователю указать тип исключения, который нужно перехватить, и код, который нужно выполнить для его обработки, например: 1015 *

public static class RecorderScope
{
   public static void Wrap(Action<Recorder> action, 
      Action<Recorder, T1> exHandler1)
      where T1: Exception
   {
      Recorder recorder = Recorder.StartTiming();
      try
      {
         action(recorder);
      }
      catch(T1 ex1)
      {
         exHandler1(recorder, ex1);
      }
      finally
      {
         recorder.Stop();
      }
   }
}

Для использования .. (Обратите внимание, вы должны указать тип исключения, так как оно, очевидно, не может быть выведено. Что вам нужно):

RecorderScope.Wrap(
   (recorder) =>
   {
      DoSomeWork();
   },
   (recorder, MyException ex) =>
   {
      recorder.ReportFailure(exception);
   });

Затем вы можете расширить этот шаблон, предоставив несколько перегрузок функции Wrap, которые принимают более одного делегата обработчика исключений. Обычно достаточно пяти перегрузок - довольно необычно для вас перехватывать более пяти различных типов исключений одновременно.

1 голос
/ 16 ноября 2009

Блок using - это блок try / finally, который вызывает dispose для рассматриваемого объекта.

Итак, это:

using(a = new A())
{
    a.Act();
}

(я думаю, точно) эквивалентно этому:

a = new A();
try
{
    a.Act();
}
finally
{
    a.Dispose();
}

И вы можете прикрепить свои уловы к концу блока попытки.

Edit:

Как альтернатива решению Роба:

Recorder recorder = Recorder.StartNew()
try
{
    DoSomeWork();
}
catch (Exception ex)
{
    recorder.ReportFailure(ex);
}
finally
{
    recorder.Dispose();
}
1 голос
/ 16 ноября 2009

Нет, блок using является только синтаксическим сахаром для блока try / finally. Это не касается попытки / ловить. На этом этапе вам придется обрабатывать его самостоятельно, так как похоже, что для ведения журнала вам нужно исключение.

1 голос
/ 16 ноября 2009

Вы можете скопировать шаблон, используемый TransactionScope, и написать оболочку, которая должна быть активно завершена - если вы не вызываете Complete(), то метод Dispose() (который вызывается в любом случае) принимает исключение и делает ваш код обработки:

using(Recorder recorder = Recorder.StartTiming()) {
    DoSomeWork();
    recorder.Complete();
}

Лично я бы остановился на try / catch - это понятнее для сопровождающих в будущем - и он предоставляет доступ к Exception.

0 голосов
/ 16 ноября 2009

Не добавляйте еще один уровень косвенности. Если вам нужно перехватить исключение, используйте try..catch..finally и вызовите Dispose() в блоке finally.

...