c # блок "finally", который работает только на исключениях - PullRequest
7 голосов
/ 10 февраля 2009

Редактировать: я посмотрел код ответа: НЕТ из них делают то, что я хочу (я проверял). Казалось бы, нет никакого способа сделать то, что я хочу в нативном C #. Я полагаю, что это не катастрофа, просто позор, учитывая, что .NET поддерживает это (см. Принятый ответ).

Спасибо всем.


У меня есть код на c # (часть тестовой среды, которая никогда не будет запускаться, кроме как в отладчике), как этот, который указывает на то, чтобы не перехватывать исключение, поскольку это делает королевскую боль отладкой кода в развернутой части стека .

Bool bad = true;
try
{
   MightThrow();
   bad = false;
}
finally
{
   if(bad) DoSomeLoggingOnFailure();

   //// Does not catch!!!! 
   //// exception continues to unwind stack.

   //// Note that re throwing the exception is NOT
   //// the same as not catching it in the first place
}

это лучший способ сделать это?

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

В частности, мне нужен отладчик для необнаруженных исключений, чтобы остановить MightThrow в стороне.

Следующее не работает, потому что не удается сделать перерыв в отладчике в правильном месте

try { ... } catch { throw; }

И это не работает, потому что оно теряет информацию о стеке (а также разрывается в неправильном месте).

try { ... } catch(Excption e) { throw e; }

Я знал, что в D я мог бы использовать scope(failure) блок

Ответы [ 16 ]

34 голосов
/ 11 февраля 2009

Итак, в .NET то, что вы просите, теоретически возможно, но это будет непросто.

CIL фактически определяет пять типов блоков обработки исключений! try, catch и finally те, к которым вы привыкли в C #, и два других:

  • filter - аналогично блоку catch, но может запускать произвольный код, чтобы определить, хочет ли он обработать ошибку, а не просто сопоставить по типу. Этот блок имеет доступ к объекту исключения и оказывает такое же влияние на трассировку стека исключений, что и блок catch.

  • fault - аналогично блоку finally, однако он запускается только при возникновении исключения. Этот блок не имеет доступа к объекту исключения и не влияет на трассировку стека исключений (как блок finally).

filter доступно на некоторых языках .NET (например, VB.NET, C ++ / CLI), но, к сожалению, недоступно в C #. Однако я не знаю ни одного языка, кроме CIL, который позволял бы выражать блок fault.

Поскольку это можно сделать в IL, значит, не все потеряно. Теоретически вы можете использовать Reflection.Emit для динамического запуска функции с блоком fault, а затем передать код, в котором вы хотите выполнить, в виде лямбда-выражений (т. Е. Один для части try, один для части fault и т. Д. ), однако (а) это не легко, и (б) я не уверен, что это действительно даст вам более полезную трассировку стека, чем вы получаете в настоящее время.

Извините, ответ не типа "вот как это сделать", но, по крайней мере, теперь вы знаете! То, что вы делаете сейчас, вероятно, лучший подход ИМХО.


Обратите внимание на тех, кто говорит, что подход, использованный в вопросе, является «плохой практикой», на самом деле это не так. Когда вы реализуете блок catch, вы говорите: «Мне нужно что-то делать с объектом исключения, когда возникает исключение», а когда вы реализуете finally, вы говорите: «Мне не нужен объект исключения, но Мне нужно что-то сделать до конца функции ".

Если вы на самом деле пытаетесь сказать: «Мне не нужен объект исключения, но мне нужно что-то делать, когда возникает исключение», тогда вы на полпути между ними, то есть вы хотите fault блок. Так как это не доступно в C #, у вас нет идеального варианта, поэтому вы можете также выбрать вариант, который с меньшей вероятностью вызовет ошибки, забыв повторно выбросить, и который не повредит трассировку стека.

27 голосов
/ 10 февраля 2009

Как насчет этого:

try
{
  MightThrow();
}
catch
{
  DoSomethingOnFailure();
  throw; // added based on new information in the original question
}

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

[ Редактировать: Уточнение]

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

[ Второе редактирование: Относительно вашего разъяснения]

В частности, мне нужен отладчик на не пойманные исключения, чтобы остановиться на исходная точка броска (в MightThrow) не в блоке catch.

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

[ Окончательное редактирование: У вас есть ответ]

kronoz вдумчиво предоставил вам ответ, который вы искали. Не нарушайте лучшие практики - используйте Visual Studio правильно! Вы можете настроить Visual Studio так, чтобы он прерывался точно при возникновении исключения. Вот официальная информация по теме .

Я на самом деле не знал об этой функции, поэтому иди и дай ему принятый ответ. Но, пожалуйста, не пытайтесь обрабатывать исключения каким-то странным способом, просто чтобы помочь себе отладить. Все, что вы делаете, это открывайте себе больше ошибок.

11 голосов
/ 10 февраля 2009

Если вы заинтересованы в том, чтобы отладчик просто останавливался именно там, где произошло исключение, рассматривали ли вы исключения первого шанса?

Если вы откроете Инструменты | Исключения, а затем отметите флажок Общеязыковые исключения во время выполнения, отладчик остановится в точке исключения независимо от каких-либо блоков try / catch / finally.

Обновление : вы можете указать точное исключение, которое хотите перехватить, развернув дерево [+] в диалоговом окне «Исключения». Хотя, конечно, он будет срабатывать каждый раз, когда любое исключение указанного типа [s] происходит [s], вы можете включать и выключать его по желанию даже в середине сеанса отладки, поэтому при разумном использовании из точек останова вы можете заставить его делать ваши ставки. Я успешно использовал его, чтобы обойти «цель вызова вызвала исключение» из-за использования отражения для создания объектов. Очень полезный инструмент в таких условиях. Также обратите внимание, что локальные данные и трассировка стека должны быть надежно доступны, насколько я помню (я только что провел быструю проверку, и они доступны ), так что никаких проблем там нет.

Конечно, если вы хотите что-то регистрировать, это выходит за рамки отладчика IDE; и в этом случае исключения первого шанса вам не помогут!

Попробуй хотя бы; Я нашел их очень полезными, и они могут быть более подходящими для вашей проблемы, чем вы думаете.

7 голосов
/ 10 февраля 2009

Для кода, который должен выполняться только для исключений, используйте блок catch:

try
{
   MightThrow();
}
catch (Exception ex)
{
   // this runs only when there was an exception
   DoSomthingOnFailure();
   // pass exception on to caller
   throw; 
}
finally
{
   // this runs everytime
   Cleanup();
}
7 голосов
/ 10 февраля 2009

Что не так с:

try
{
   MightThrow();
}
catch
{
   DoSomthingOnFailure();
   throw;
}
4 голосов
/ 10 февраля 2009

Это то, что вы хотите. Этот метод будет вызываться только при возникновении ошибки, а оператор throw перезапустит исключение без изменений стека вызовов.

try
{
   MightThrow();
}
catch
{
   DoSomthingOnFailure();
   throw;
}
3 голосов
/ 10 февраля 2009

Блок finally, который запускается только при сбое, называется catch (без параметров). : -)

Теперь есть небольшая оговорка. Если вы хотите иметь специализированный случай «catch» для определенного типа исключения , а имеет универсальный «catch», который работает для всех исключений, вам придется сделать немного пользовательской логики. *

Таким образом, я бы сделал что-то вроде:

  try
  {
    MightThrow();
  }
  catch(MyException ex)
  {
    // Runs on MyException
    MySpecificFailureHandler()
    // Since we have handled the exception and can't execute the generic
    // "catch" block below, we need to explicitly run the generic failure handler
    MyGenericFailureHandler()
  }
  catch
  {
    // Runs on any exception hot handled specifically before
    MyGenericFailureHandler()
    // If you want to mimic "finally" behavior and propagate the exception
    // up the call stack
    throw;
  }
  finally
  {
    // Runs on any failure or success
    MyGenericCleanupHandler();
  }
2 голосов
/ 11 февраля 2009

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

    public  class Executor
{
    private readonly Action mainActionDelegate;
    private readonly Action onFaultDelegate;

    public Executor(Action mainAction, Action onFault)
    {
        mainActionDelegate = mainAction;
        onFaultDelegate = onFault;
    }

    public  void Run()
    {
        bool bad = true;
        try
        {
            mainActionDelegate();
            bad = false;
        }
        finally
        {
            if(bad)
            {
                onFaultDelegate();
            }
        }
    }

}

И используйте его как:

            new Executor(MightThrow, DoSomeLoggingOnFailure).Run();

Надеюсь, это поможет.

2 голосов
/ 11 февраля 2009

Позвольте мне повторить ваши требования так, как я их понимаю:

  1. Требуется некоторый код, который запускается только при генерировании исключения, чтобы выполнить некоторую регистрацию.
  2. Вы хотите запустить тестовый фреймворк под отладчиком и прервать его в момент, когда выдается исключение.

Чтобы соответствовать первому требованию, вы должны написать код так, как все предлагали - используя без параметров и поймать.

Чтобы удовлетворить ваше второе требование при использовании перехвата без параметров, вы можете настроить отладчик так, чтобы он прерывался при возникновении исключения, а не только при обработке необработанного исключения. Я подозреваю, что вы знаете, как это сделать, но я поставлю это здесь для полноты ответа: в VS вы можете сделать это в Debug -> Exception -> Common Language Runtime Exceptions -> установите флажок Thrown.

Если вы знаете, что ваше приложение выдает много обработанных исключений, это может не подойти вам. В этот момент ваш единственный выбор, удовлетворяющий вашим первым требованиям, - это либо написать код, который в конечном итоге использовать для целей регистрации исключений, либо изучить прямой путь передачи IL, как предлагает Грег Бич.

Однако, выполняется ли код finally, зависит от используемого отладчика. В частности, VS прервется на незапрошенное исключение перед выполнением finally и не позволит вам продолжить. Таким образом, если вы не отсоединитесь от процесса в этот момент, ваш код регистрации никогда не будет выполнен. Другими словами, второе требование будет мешать выполнению первого требования.

2 голосов
/ 11 февраля 2009

Как насчет только перехвата исключения, которое "MightThrow" не выбрасывает?

Bool bad = true;
try
{
   MightThrow();
   bad = false;
}
catch (SomePrivateMadeUpException foo)
{ 
   //empty
}
finally
{
   if(bad) DoSomeLoggingOnFailure();   
}
...