Рекомендации по перехвату и повторному выбрасыванию.ЧИСТЫЕ исключения

StackOverflow https://stackoverflow.com/questions/22623

Вопрос

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

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

Против:

try
{
    //some code
}
catch
{
    throw;
}
Это было полезно?

Решение

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

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

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

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

Карл Сеген имеет отличная статья об обработке исключений в его электронная книга "основы программирования" кроме того, это отличное чтение.

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

Другие советы

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

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

На самом деле, есть некоторые ситуации, в которых throw statment не сохранит информацию о 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

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

Вызов исключения.InternalPreserveStackTrace

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

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

У меня есть недостаток в том, что я полагаюсь на частный метод для сохранения информации StackTrace.Это может быть изменено в будущих версиях .NET Framework.Приведенный выше пример кода и предлагаемое ниже решение были извлечены из Блог Фабриса МАРГЕРИ.

Вызывающее исключение.SetObjectData

Приведенная ниже методика была предложена Антон Тихий в качестве ответа на В C #, как я могу повторно создать 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
)

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

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

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

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

Никто не объяснил разницу между 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, поскольку номер строки исходного исключения сохраняется, но не является реальным повторным сбросом, поскольку он изменяет тип исходного исключения.

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

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

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

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

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

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

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

В принципе, MSIL (CIL) имеет две инструкции - "выбросить" и "повторно выбросить".:

  • C # "throw ex;" компилируется в MSIL "throw"
  • C # 's "throw;" - в MSIL "повторно вбрасывать"!

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

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

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

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

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

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;
}

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

К вашему сведению, я только что протестировал это, и трассировка стека, о которой сообщает '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() вызвал исключение.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top