f# pattern matching with types
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?
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)
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
...
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."
Are you sure the program is not behaving as expected? The debugger spans are not always reliable.
You want something like this instead.
let rec printObj (o : obj) (sb : StringBuilder) (depth : int)
let props = o.GetType().GetProperties() :> IEnumerable<PropertyInfo>
let enumer = props.GetEnumerator()
while enumer.MoveNext() do
let currObj = (enumer.Current.GetValue (o, null)) :> 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
This is coming from the MSDN docs on the Array
class:
In the .NET Framework version 2.0, the Array class implements the System.Collections.Generic.IList, System.Collections.Generic.ICollection, and System.Collections.Generic.IEnumerable generic interfaces. The implementations are provided to arrays at run time, and therefore are not visible to the documentation build tools. As a result, the generic interfaces do not appear in the declaration syntax for the Array class, and there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.
精彩评论