Лучшие практики для отлова и повторного выброса исключений .NET - PullRequest
271 голосов
/ 22 августа 2008

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

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

Vs:

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

Ответы [ 11 ]

256 голосов
/ 22 августа 2008

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

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

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

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

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

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

96 голосов
/ 22 августа 2008

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

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}
26 голосов
/ 01 июля 2012

На самом деле, есть ситуации, когда 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:

Вызов исключения. 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 weblog .

Вызов Exception.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
)

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

19 голосов
/ 22 августа 2008

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

13 голосов
/ 22 августа 2008

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

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

9 голосов
/ 14 ноября 2016

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

8 голосов
/ 08 февраля 2012

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

8 голосов
/ 05 июля 2010

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

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

В основном MSIL (CIL) имеет две инструкции - «бросить» и «перебросить»:

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

По сути, я вижу причину, по которой «throw ex» переопределяет трассировку стека.

3 голосов
/ 22 августа 2008

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

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

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

3 голосов
/ 22 августа 2008

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

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

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

...