【发布时间】:2017-05-02 19:05:16
【问题描述】:
TL;DR:如何在以后引发先前捕获的异常,同时保留原始异常的堆栈跟踪。
因为我认为这对Result monad 或计算表达式很有用,尤其是。由于该模式通常用于包装异常而不抛出异常,因此这里有一个示例:
type Result<'TResult, 'TError> =
| Success of 'TResult
| Fail of 'TError
module Result =
let bind f =
function
| Success v -> f v
| Fail e -> Fail e
let create v = Success v
let retnFrom v = v
type ResultBuilder () =
member __.Bind (m , f) = bind f m
member __.Return (v) = create v
member __.ReturnFrom (v) = retnFrom v
member __.Delay (f) = f
member __.Run (f) = f()
member __.TryWith (body, handler) =
try __.Run body
with e -> handler e
[<AutoOpen>]
module ResultBuilder =
let result = Result.ResultBuilder()
现在让我们使用它:
module Extern =
let calc x y = x / y
module TestRes =
let testme() =
result {
let (x, y) = 10, 0
try
return Extern.calc x y
with e ->
return! Fail e
}
|> function
| Success v -> v
| Fail ex -> raise ex // want to preserve original exn's stacktrace here
问题在于堆栈跟踪将不包括异常的来源(这里是calc 函数)。如果我按照编写的代码运行,它会抛出如下错误,这没有提供错误来源的信息:
System.DivideByZeroException : Attempted to divide by zero.
at Microsoft.FSharp.Core.Operators.Raise[T](Exception exn)
at PlayFul.TestRes.testme() in D:\Experiments\Play.fs:line 197
at PlayFul.Tests.TryItOut() in D:\Experiments\Play.fs:line 203
使用reraise() 是行不通的,它需要一个捕获上下文。显然,以下 kind-a 有效,但由于嵌套异常使调试变得更加困难,并且如果在深堆栈中多次调用此 wrap-reraise-wrap-reraise 模式,可能会变得非常难看。
System.Exception("Oops", ex)
|> raise
更新:TeaDrivenDev 在 cmets 中建议使用 ExceptionDispatchInfo.Capture(ex).Throw(),这可行,但需要将异常包装在其他东西中,使模型复杂化。但是,它确实保留了堆栈跟踪,并且可以将其制成相当可行的解决方案。
【问题讨论】:
-
我不确定
reraise到底是做什么的,但如果你使用的是 .NET 4.5,你可以试试ExceptionDispatchInfo.Capture(ex).Throw()。 -
@TeaDrivenDev 听起来很有趣/很酷/还有更多,我不知道添加了这种语言。调查它...
-
将异常包装在另一个中。
-
@FyodorSoikin,这不是我最后的后备建议,还是你的意思是别的?即:
raise (System.Exception("Oops", ex)). -
是的,我就是这个意思。抱歉,没有马上注意到。
标签: f# try-catch monads computation-expression