Рекомендации по перехвату и повторному запуску .NET-исключений

Каковы наилучшие методы, которые следует учитывать при перехвате исключений и их повторном броске? Я хочу убедиться, что объект Exception object InnerException и трассировка стека сохраняются. Есть ли разница между следующими кодовыми блоками в том, как они справляются с этим?

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

Vs:

try
{
    //some code
}
catch
{
    throw;
}

Ответ 1

Способ сохранения трассировки стека заключается в использовании throw; Это также верно

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

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

Майк также правилен, если исключение позволяет вам передать исключение (которое рекомендуется).

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

Изменить: Рабочая ссылка на Основы программирования. pdf. Просто найдите текст для "исключения".

Ответ 2

Если вы создадите новое исключение с начальным исключением, вы также сохраните начальную трассировку стека.

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

Ответ 3

На самом деле, есть ситуации, в которых статус throw не сохранит информацию StackTrace. Например, в приведенном ниже коде:

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

StackTrace укажет, что строка 54 вызвала исключение, хотя она была поднята в строке 47.

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowIncomplete() in Program.cs:line 54
   at Program.Main(String[] args) in Program.cs:line 106

В ситуациях, подобных описанным выше, существует два варианта предварительной настройки исходного StackTrace:

Вызов Exception.InternalPreserveStackTrace

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

private static void PreserveStackTrace(Exception exception)
{
  MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
  preserveStackTrace.Invoke(exception, null);
}

У меня есть недостаток - полагаться на частный метод для сохранения информации StackTrace. Его можно изменить в будущих версиях .NET Framework. Пример кода выше и предлагаемое ниже решение было извлечено из веб-сайта Fabrice MARGUERIE.

Вызов Exception.SetObjectData​​strong >

Ниже была предложена техника Антон Тихий в качестве ответа на В С#, как я могу перебросить InnerException без потери трассировки стека вопрос.

static void PreserveStackTrace (Exception e) 
{ 
  var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ; 
  var mgr = new ObjectManager     (null, ctx) ; 
  var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ; 

  e.GetObjectData    (si, ctx)  ; 
  mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData 
  mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData 

  // voila, e is unmodified save for _remoteStackTraceString 
} 

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

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

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

Ответ 4

Когда вы throw ex, вы по существу бросаете новое исключение и пропустите исходную информацию трассировки стека. throw является предпочтительным методом.

Ответ 5

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

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

Ответ 6

Вы всегда должны использовать "throw" ; для отмены исключений в .NET,

Обратитесь к этому, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

В основном MSIL (CIL) имеет две инструкции: "throw" и "rethrow":

  • С# "throw ex;" скомпилируется в MSIL "throw"
  • С# "throw;" - в MSIL "rethrow"!

В принципе, я вижу причину, по которой "throw ex" переопределяет трассировку стека.

Ответ 7

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

Рассмотрим следующий код:

static void Main(string[] args)
{
    try
    {
        TestMe();
    }
    catch (Exception ex)
    {
        string ss = ex.ToString();
    }
}

static void TestMe()
{
    try
    {
        //here some code that will generate an exception - line #17
    }
    catch (Exception ex)
    {
        //throw new ApplicationException(ex.ToString());
        throw ex; // line# 22
    }
}

Когда вы выполняете либо "бросок", либо "throw ex", вы получаете трассировку стека, но строка # будет # 22, поэтому вы не можете понять, какая строка точно выбрала исключение (если только у вас нет 1 или несколько строк кода в блоке try). Чтобы получить ожидаемую строку # 17 в вашем исключении, вам придется выбросить новое исключение с исходной трассировкой стека.

Ответ 8

Никто не объяснил разницу между ExceptionDispatchInfo.Capture( ex ).Throw() и простой throw, так что вот оно. Однако некоторые люди заметили проблему с throw.

Полный способ перебора пойманного исключения - использовать ExceptionDispatchInfo.Capture( ex ).Throw() (доступно только из .Net 4.5).

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

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

Случай 1 и случай 2 дадут вам трассировку стека, где номер строки исходного кода для метода CallingMethod - номер строки строки throw new Exception( "TEST" ).

Однако случай 3 даст вам трассировку стека, где номер строки исходного кода для метода CallingMethod - номер строки вызова throw. Это означает, что если строка throw new Exception( "TEST" ) окружена другими операциями, вы не имеете представления о том, в каком номере строки было исключено исключение.

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

Ответ 9

Я бы определенно использовал:

try
{
    //some code
}
catch
{
    //you should totally do something here, but feel free to rethrow
    //if you need to send the exception up the stack.
    throw;
}

Это сохранит ваш стек.

Ответ 10

Вы также можете использовать:

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

И любые выброшенные выбросы выйдут на следующий уровень, который их обработает.

Ответ 11

FYI Я только что проверил это и трассировку стека, сообщенную "throw"; не является полностью корректной трассировкой стека. Пример:

    private void foo()
    {
        try
        {
            bar(3);
            bar(2);
            bar(1);
            bar(0);
        }
        catch(DivideByZeroException)
        {
            //log message and rethrow...
            throw;
        }
    }

    private void bar(int b)
    {
        int a = 1;
        int c = a/b;  // Generate divide by zero exception.
    }

Трассировка стека точно указывает на начало исключения (номер строки сообщения), но номер строки, сообщенный для foo(), является линией броска;, поэтому вы не можете определить, какой из вызовов bar() вызвал исключение.