paul
paul

Reputation: 1705

F# Exception Not Being Caught Correctly

I have an F# exception that it is not being caught in the correct catch block.

Here's the relevant code:

exception ConfigFileVersionIncompatabilityException of string

[<XmlType("config")>]
type Configuration() = class

    let thisVersion : string = "1.0"
    let mutable fileVersion : string = thisVersion

    [<XmlAttribute("version")>]
    member x.FileVersion
        with get() = fileVersion
        and set v = if v <> thisVersion
                    then raise (ConfigFileVersionIncompatabilityException(String.Format("Was expecting version {0} but read version {1}.", thisVersion, v)))
end


module FilterFileFunctions =

    let sampleConfigFilename = "sample.filters"

    let readConfig (file : string) =
        try
            use xmlDoc = new StreamReader(file) in
                let s = XmlSerializer(typeof<Configuration>)
                s.Deserialize(xmlDoc) :?> Configuration |> Success
        with
        | ConfigFileVersionIncompatabilityException(s) ->
            String.Format("Failed to read the configuration file: \"{0}\";\nThe following reason was given:\n{1}", file, s)
            |> Failure
        | ex ->
            String.Format("Failed to read the configuration file: \"{0}\";\n{1}", file, ex)
            |> Failure

The problem is that the ex catch block catches the ConfigFileVersionIncompatabilityException exception, where it should be caught by the first block.

I tried to use :? System.Exception as ex instead of just ex and it still behaved the same.

Am I missing something?

[Edited 1 minute after initial post to remove irrelevant code.]

Upvotes: 0

Views: 296

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243041

When an exception occurs during the deserialization, the Deserialize method will catch it and wrap it inside InvalidOperationException. This means that you need to chatch InvalidOperationException and then analyze the InnerException property to get to your user-defined exception.

try // ..
with 
| :? InvalidOperationException as invOp ->
   match inv.InnerException with 
   | :? ConfigFileVersionIncompatabilityException as e -> 
     printfn "%s" e.Data0
   | _ -> // generic handler
| e -> // generic handler

The Data0 property exposes the value carried by the exception (I used it, because you cannot access it easily in the pattern matching when using :?). However, you can avoid the ugly nesting of match expressions (and the duplication of generic handlers) using active patterns:

let (|InnerException|) (e:exn) =
    e.InnerException

try // ..
with 
| InnerException(ConfigFileVersionIncompatabilityException s) -> 
   printfn "%s" s
| _ -> // generic handler

Upvotes: 6

Related Questions