Reputation: 1687
I am trying to capture a specific exception to acquire the underlying message for logging but I don't want the exception passed back to the calling method. I know how to do this in C#, F#? meh.
Here is the code that works w/o targeting a specific exception type. I simply log that a problem occurred within the method
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.")
| _ -> ()
In the following code I have targeted to evaluate a NullReferenceException type but it could be any type of error really
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)
| _ -> ()
The problem I'm seeing is when I specify an Exception type the exception gets passed by up the chain to the calling method. The first method works as is but it isn't collecting very useful information.
The 2nd method would be the way to go to capture more specifics of the error but it always carries the exception up the chain.
How can my 2nd version of the code be minimally modified (if possible) to prevent the error from being passed back to the caller? Pretty sure there is an easy way to fix this issue but I'm not 100% how in F#
Upvotes: 1
Views: 492
Reputation: 80744
Just like in C#, you can have several patterns in the with
block. They will be matched in order. Use the first one to catch the exception you'd like to log, then use a catch-all pattern to swallow all other exceptions:
try
...
with
| ?: NullReferenceException as ne -> logger.LogError ...
| _ -> ()
EDIT in response to comments.
If ?: is the catch what is _ -> () and why is it needed?
Both :?
and _ ->
are "the catch".
In F#, just like in C#, one can have multiple catch
blocks - several for different types of exceptions and, optionally, one catch-all block, for all exception types not explicitly mentioned in other blocks.
For example, in 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"); }
Equivalent code in 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"
The F# version does have a bit more flexibility - e.g. you can use when
guards or deeply pattern-match on F#-defined exception types. But the idea is the same: multiple catchers, tested in succession until one matches.
Another thing to note is that the syntax :?
is not special to exception handling. It's syntax for pattern-matching on class hierarchy. For example, you can use it in a regular function:
let isString (o: obj) =
match o with
| :? string -> true
| _ -> false
I would think that rather somewhat of stating a "final" ending action
The words "rather" and "somewhat" belong to natural languages. Programming language constructs, on the other hand, have very specific, strictly defined meanings, which may or may not match your intuition at any given moment.
In particular, the finally
block in C#/F# is not the same as "catching all other exceptions". While exception catchers execute only when an exception has happened, the finally
block executes regardless of whether an exception has happened.
When no exception happens, the finally
block executes after try
as normal. When an exception does happen, the finally
block executes before the exception is raised, and, crucially, the exception flight continues after the finally
block is done executing. Just to drive this home, one more time: the finally
block does not stop the exception.
The idea of the finally
block is to allow you to clean up resources and be sure that the cleanup happens whether or not an exception occurs.
In C#:
try { Console.WriteLine("Working"); }
finally { Console.WriteLine("Done"); }
In F#:
try
printfn "Working"
finally
printfn "Done"
Another thing to note is that, unlike C#, F# does not allow to have both with
and finally
in the same block for complicated reasons. So if you need both, you have to nest them inside each other.
In C#:
try { Console.WriteLine("Working"); }
catch (NullReferenceException e) { Console.WriteLine("Null"); }
finally { Console.WriteLine("Cleanup"); }
In F#:
try
try
printfn "Working"
with :? NullReferenceException ->
printfn "Null"
finally
printfn "Done"
For more information, see the docs.
Upvotes: 4