Едно от нещата, от които се страхувах, е, че след като третирате изключение като нормален обект и го прехвърлите, няма да можете да го повдигнете отново и да запазите оригиналния му стек.
Но това е вярно само ако направите, между или в края, 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 и се случи при Запазване 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
Обвийте изключението в изключение
Това беше предложено от Fyodor Soikin и вероятно е начинът по подразбиране за .NET, тъй като се използва в много случаи в BCL. Въпреки това, това води до по-малко полезно проследяване на стека в много ситуации и, 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