Рекомендации по перехвату и повторному запуску .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.SetObjectDatastrong >
Ниже была предложена техника Антон Тихий в качестве ответа на В С#, как я могу перебросить 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() вызвал исключение.