Рекомендации по перехвату и повторному выбрасыванию.ЧИСТЫЕ исключения
-
09-06-2019 - |
Вопрос
Какие рекомендации следует учитывать при перехвате исключений и их повторном выбрасывании?Я хочу убедиться, что 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() вызвал исключение.