Одна из вещей, которых я боялся, заключается в том, что, как только вы обработаете исключение как обычный объект и передадите его, вы не сможете вызвать его снова и сохранить исходную трассировку стека.
Но это верно только в том случае, если вы делаете между ними или в конце raise excn
.
Я взял все идеи из комментариев и показываю их здесь в виде трех решений проблемы. Выберите то, что кажется вам наиболее естественным.
Захватите трассировку стека с помощью ExceptionDispatchInfo
В следующем примере показано предложение TeaDrivenDev в действии с использованием ExceptionDispatchInfo.Capture
.
type Ex =
/// Capture exception (.NET 4.5+), keep the stack, add current stack.
/// This puts the origin point of the exception on top of the stacktrace.
/// It also adds a line in the trace:
/// "--- End of stack trace from previous location where exception was thrown ---"
static member inline throwCapture ex =
ExceptionDispatchInfo.Capture ex
|> fun disp -> disp.Throw()
failwith "Unreachable code reached."
В примере в исходном вопросе (замените raise ex
) будет создана следующая трассировка (обратите внимание на строку с "--- Конец трассировки стека из предыдущего места, где было выдано исключение ---") :
System.DivideByZeroException : Attempted to divide by zero.
at Playful.Ex.Extern.calc(Int32 x, Int32 y) in R:\path\Ex.fs:line 118
at [email protected](Unit unitVar) in R:\path\Ex.fs:line 137
at Playful.Ex.Result.ResultBuilder.Run[b](FSharpFunc`2 f) in R:\path\Ex.fs:line 103
at Playful.Ex.Result.ResultBuilder.TryWith[a](FSharpFunc`2 body, FSharpFunc`2 handler) in R:\path\Ex.fs:line 105
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Playful.Ex.TestRes.testme() in R:\path\Ex.fs:line 146
at Playful.Ex.Tests.TryItOut() in R:\path\Ex.fs:line 153
Полностью сохранить трассировку стека
Если у вас нет .NET 4.5 или вам не нравится добавленная строка в середине трассировки ("--- Конец трассировки стека из предыдущего места, где было выдано исключение ---"), то вы можете сохранить стек и добавить текущую трассировку за один раз.
Я нашел это решение, следуя решению TeaDrivenDev, и наткнулся на Preserving stacktrace при повторной генерации исключений.
type Ex =
/// Modify the exception, preserve the stacktrace and add the current stack, then throw (.NET 2.0+).
/// This puts the origin point of the exception on top of the stacktrace.
static member inline throwPreserve ex =
let preserveStackTrace =
typeof<Exception>.GetMethod("InternalPreserveStackTrace", BindingFlags.Instance ||| BindingFlags.NonPublic)
(ex, null)
|> preserveStackTrace.Invoke // alters the exn, preserves its stacktrace
|> ignore
raise ex
В примере в исходном вопросе (замените raise ex
) вы увидите, что трассировки стека хорошо связаны и что источник исключения находится сверху, где он должен быть:
System.DivideByZeroException : Attempted to divide by zero.
at Playful.Ex.Extern.calc(Int32 x, Int32 y) in R:\path\Ex.fs:line 118
at [email protected](Unit unitVar) in R:\path\Ex.fs:line 137
at Playful.Ex.Result.ResultBuilder.Run[b](FSharpFunc`2 f) in R:\path\Ex.fs:line 103
at Playful.Ex.Result.ResultBuilder.TryWith[a](FSharpFunc`2 body, FSharpFunc`2 handler) in R:\path\Ex.fs:line 105
at Microsoft.FSharp.Core.Operators.Raise[T](Exception exn)
at Playful.Ex.TestRes.testme() in R:\path\Ex.fs:line 146
at Playful.Ex.Tests.TryItOut() in R:\path\Ex.fs:line 153
Оберните исключение в исключение
Это было предложено Федором Сойкиным и, вероятно, является способом .NET по умолчанию, поскольку он используется во многих случаях в БКЛ. Однако во многих ситуациях это приводит к менее полезной трассировке стека и, imo, может привести к запутанной трассировке вверх ногами в глубоко вложенных функциях.
type Ex =
/// Wrap the exception, this will put the Core.Raise on top of the stacktrace.
/// This puts the origin of the exception somewhere in the middle when printed, or nested in the exception hierarchy.
static member inline throwWrapped ex =
exn("Oops", ex)
|> raise
Применяя так же (замените raise ex
), как и в предыдущих примерах, это даст вам трассировку стека следующим образом. В частности, обратите внимание, что корень исключения, функция calc
, теперь находится где-то посередине (здесь все еще довольно очевидно, но в глубоких трассировках с несколькими вложенными исключениями уже не так сильно).
Также обратите внимание, что это дамп трассировки, учитывающий вложенное исключение. Когда вы отлаживаете, вам нужно щелкнуть по всем вложенным исключениям (и понять, являются ли они вложенными с самого начала).
System.Exception : Oops
----> System.DivideByZeroException : Attempted to divide by zero.
at Microsoft.FSharp.Core.Operators.Raise[T](Exception exn)
at Playful.Ex.TestRes.testme() in R:\path\Ex.fs:line 146
at Playful.Ex.Tests.TryItOut() in R:\path\Ex.fs:line 153
--DivideByZeroException
at Playful.Ex.Extern.calc(Int32 x, Int32 y) in R:\path\Ex.fs:line 118
at [email protected](Unit unitVar) in R:\path\Ex.fs:line 137
at Playful.Ex.Result.ResultBuilder.Run[b](FSharpFunc`2 f) in R:\path\Ex.fs:line 103
at Playful.Ex.Result.ResultBuilder.TryWith[a](FSharpFunc`2 body, FSharpFunc`2 handler) in R:\path\Ex.fs:line 105
Вывод
Я не говорю, что один подход лучше другого. Для меня просто бездумно делать raise ex
не очень хорошая идея, если только ex
не является недавно созданным и ранее не вызываемым исключением.
Прелесть в том, что reraise()
фактически делает то же самое, что и Ex.throwPreserve
выше. Поэтому, если вы считаете, что reraise()
(или throw
без аргументов в C#) — хороший шаблон программирования, вы можете использовать его. Единственная разница между reraise()
и Ex.throwPreserve
заключается в том, что последний не требует контекста catch
, что, как мне кажется, дает огромное преимущество в удобстве использования.
Я думаю, в конце концов, это дело вкуса и того, к чему вы привыкли. Для меня я просто хочу, чтобы причина исключения была на первом месте. Большое спасибо первому комментатору, TeaDrivenDev, который направил меня к усовершенствованию .NET 4.5, что само по себе привело ко второму подходу выше. .
(извините, что отвечаю на мой собственный вопрос, но, поскольку никто из комментаторов этого не сделал, я решил активизироваться ;)
person
Abel
schedule
17.12.2016
reraise
, но если вы используете .NET 4.5, вы можете попробоватьExceptionDispatchInfo.Capture(ex).Throw()
. - person TeaDrivenDev   schedule 17.12.2016raise (System.Exception("Oops", ex))
. - person Abel   schedule 17.12.2016