如何捕获特定类型的异常而不将结果传递给 F# 中的调用方?

How do you catch a specific type of exception without passing the results to the caller in F#?

我正在尝试捕获特定异常以获取用于记录的基础消息,但我不想将异常传递回调用方法。我知道如何在 C#、F# 中执行此操作?嗯。

这是针对特定异常类型的 w/o 代码。我只是记录方法中发生的问题

        match remoteApiResponse.StatusCode with
        | HttpStatusCode.OK ->
            let! resp = remoteApiResponse.Content.ReadAsStringAsync() |> Async.AwaitTask
            try
                let IsSuccess = JObject.Parse(resp).["IsSuccess"].ToString().Trim().ToLower()
                match IsSuccess with
                | "true" -> ()
                | "false" -> remoteApiResponse.StatusCode <- HttpStatusCode.BadRequest
                | _ -> ()
            with 
            | _ ->  logger.LogError("Error Occurred during IsSuccess flag validation.") 
        | _ -> ()

在下面的代码中,我的目标是评估 NullReferenceException 类型,但实际上它可能是任何类型的错误

    match remoteApiResponse.StatusCode with
        | HttpStatusCode.OK ->
            let! resp = remoteApiResponse.Content.ReadAsStringAsync() |> Async.AwaitTask
            try
                let IsSuccess = JObject.Parse(resp).["IsSuccess"].ToString().Trim().ToLower()
                match IsSuccess with
                | "true" -> ()
                | "false" -> remoteApiResponse.StatusCode <- HttpStatusCode.BadRequest
                | _ -> ()
            with 
            | :? NullReferenceException as ne -> logger.LogError("Error Occurred during IsSuccess flag validation." + ne.Message) 
        | _ -> ()

我遇到的问题是,当我指定异常类型时,异常会沿着链向上传递到调用方法。第一种方法按原样工作,但它没有收集非常有用的信息。

第二种方法是捕获更多错误细节的方法,但它总是将异常向上传递。

如何对我的第二版代码进行最少的修改(如果可能)以防止将错误传回给调用者? 很确定有一种简单的方法可以解决这个问题,但我不是 100% 在 F#

中如何解决

就像在 C# 中一样,您可以在 with 块中有多个模式。它们将按顺序匹配。使用第一个捕获您想要记录的异常,然后使用捕获所有模式来吞下所有其他异常:

try
  ...
with
| ?: NullReferenceException as ne -> logger.LogError ...
| _ -> ()

编辑 回应评论。

If ?: is the catch what is _ -> () and why is it needed?

:?_ -> 都是“捕获物”。

在 F# 中,就像在 C# 中一样,一个人可以有多个 catch 块 - 几个用于不同类型的异常,并且可选地,一个包罗万象的块,用于其他块中未明确提及的所有异常类型.

例如,在 C# 中:

try { throw ... }
catch (NullReferenceException e) { Console.WriteLine("Null"); }
catch (NotImplementedException e) { Console.WriteLine("Not impl"); }
catch (InvalidOperationException e) { Console.WriteLine("Invalid op"); }
catch { Console.WriteLine("No idea what happened"); }

F# 中的等效代码:

try
  ...
with
| ?: NullReferenceException as e -> printfn "Null"
| ?: NotImplementedException as e -> printfn "Not impl"
| ?: InvalidOperationException as e -> printfn "Invalid op"
| _ -> printfn "No idea what happened"

F# 版本确实具有更多的灵活性 - 例如你可以在 F#-defined exception types 上使用 when 守卫或深度模式匹配。但想法是一样的:多个捕手,连续测试直到一个匹配。

另外需要注意的是语法:?并不是异常处理的特殊语法。它是 class 层次结构上模式匹配的语法。例如,您可以在常规函数中使用它:

let isString (o: obj) = 
  match o with 
  | :? string -> true
  | _ -> false

I would think that rather somewhat of stating a "final" ending action

“rather”和“somewhat”这两个词属于自然语言。另一方面,编程语言结构具有非常具体、严格定义的含义,在任何给定时刻可能符合也可能不符合您的直觉。

特别是,C#/F# 中的 finally 块与“捕获所有其他异常”不同。异常捕获器仅在异常发生时执行,而 finally 块执行 不管 是否发生异常。

当没有异常发生时,finally块在try之后正常执行。当确实发生异常时,finally 块在 引发异常之前执行 ,并且至关重要的是,异常飞行在 finally 块执行完毕后继续。只是为了开车回家,再来一次:finally 不会停止异常 .

finally 块的想法是允许您清理资源并确保无论是否发生异常都会进行清理。

在 C# 中:

try { Console.WriteLine("Working"); }
finally { Console.WriteLine("Done"); }

在 F# 中:

try
  printfn "Working"
finally
  printfn "Done"

另一件需要注意的事情是,与 C# 不同,F# 不允许在同一块中同时使用 withfinally,原因很复杂。所以如果你需要两者,你必须将它们嵌套在一起。

在 C# 中:

try { Console.WriteLine("Working"); }
catch (NullReferenceException e) { Console.WriteLine("Null"); }
finally { Console.WriteLine("Cleanup"); }

在 F# 中:

try
  try
    printfn "Working"
  with :? NullReferenceException ->
    printfn "Null"
finally
  printfn "Done"

有关详细信息,请参阅 the docs