f# pattern matching with types

19,244

Solution 1

Here is how I got it to work...

 let getMethod = prop.GetGetMethod()
 let value = getMethod.Invoke(o, Array.empty)
     ignore <|
         match value with
         | :? float as f -> sb.Append(f.ToString() + ", ") |> ignore
                            ...

Solution 2

As others has pointed out, you need to invoke the GetValue member to get the value of the property - the iteration that you implemented iterates over PropertyInfo objects, which are "descriptors of the property" - not actual values. However, I don't quite understand why are you using GetEnumerator and while loop explicitly when the same thing can be written using for loop.

Also, you don't need to ignore the value returned by the sb.Append call - you can simply return it as the overall result (because it is the StringBuilder). This will actually make the code more efficient (because it enables tail-call optimizataion). As a last point, you don't need ToString in sb.Append(..), because the Append method is overloaded and works for all standard types.

So after a few simplification, you can get something like this (it's not really using the depth parameter, but I guess you want to use it for something later on):

let rec printObj (o : obj) (sb : StringBuilder) (depth : int) =
  let props = o.GetType().GetProperties() 
  for propInfo in props do
    let propValue = propInfo.GetValue(o, null)
    match propValue with 
    | :? string as s -> sb.Append(s) 
    | :? bool as c -> sb.Append(c) 
    | :? int as i -> sb.Append(i) 
    | :? float as i -> sb.Append(i) 
    | _ ->  printObj currObj sb (depth + 1) 

Solution 3

In your example, enumer.Current is an object containing a PropertyInfo. This means that currObj is always a PropertyInfo object, and will always correspond to the last case in your match statement.

Since you're interested in the type of the value of the property, you'll need to call the GetValue() method of the PropertyInfo to get to the actual value of the property (as in ChaosPandion's answer).

Since an Enumerator returns its values as objects, you'll also need to cast the enum.current to a PropertyInfo before you can access GetValue.

Try replacing

let currObj = (enumer.Current : obj)

with

let currObj = unbox<PropertyInfo>(enumer.Current).GetValue (o, null)

With this change, I can get your code to work (in FSI):

>  let test = {authorId = 42; authorName = "Adams"; firm = {firmId = 1; firmName = "GloboCorp inc."} };;
> string <| printObj test (new StringBuilder()) 1;;
val it : string = "42Adams1GloboCorp inc."
Share:
19,244
PhilBrown
Author by

PhilBrown

merge delete

Updated on June 03, 2022

Comments

  • PhilBrown
    PhilBrown almost 2 years

    I'm trying to recursively print out all an objects properties and sub-type properties etc. My object model is as follows...

    type suggestedFooWidget = {
        value: float ; 
        hasIncreasedSinceLastPeriod: bool ;
    }
    
    type firmIdentifier = {
        firmId: int ;
        firmName: string ;
    }
    type authorIdentifier = {
        authorId: int ;
        authorName: string ;
        firm: firmIdentifier ;
    }
    
    type denormalizedSuggestedFooWidgets = {
        id: int ; 
        ticker: string ;
        direction: string ;
        author: authorIdentifier ;
        totalAbsoluteWidget: suggestedFooWidget ;
        totalSectorWidget: suggestedFooWidget ;
        totalExchangeWidget: suggestedFooWidget ;
        todaysAbsoluteWidget: suggestedFooWidget ;
        msdAbsoluteWidget: suggestedFooWidget ;
        msdSectorWidget: suggestedFooWidget ;
        msdExchangeWidget: suggestedFooWidget ;
    }
    

    And my recursion is based on the following pattern matching...

    let rec printObj (o : obj) (sb : StringBuilder) (depth : int) 
        let props = o.GetType().GetProperties()
        let enumer = props.GetEnumerator()
        while enumer.MoveNext() do
            let currObj = (enumer.Current : obj)
            ignore <|
                 match currObj with
                 | :? string as s -> sb.Append(s.ToString())
                 | :? bool as c -> sb.Append(c.ToString())
                 | :? int as i -> sb.Append(i.ToString())
                 | :? float as i -> sb.Append(i.ToString())
                 | _ ->  printObj currObj sb (depth + 1)
        sb
    

    In the debugger I see that currObj is of type string, int, float, etc but it always jumps to the defualt case at the bottom. Any idea why this is happening?