f#模式与类型匹配

时间:2010-06-07 18:36:41

标签: f# types pattern-matching

我正在尝试以递归方式打印出所有对象属性和子类型属性等。我的对象模型如下...

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 ;
}

我的递归基于以下模式匹配...

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

在调试器中,我看到currObj的类型为string,int,float等,但它总是跳转到底部的d​​efualt case。知道为什么会这样吗?

5 个答案:

答案 0 :(得分:15)

正如其他人所指出的那样,你需要调用GetValue成员来获取属性的值 - 你实现的迭代迭代PropertyInfo个对象,它们是“属性的描述符” - 不是实际值。但是,我不太明白为什么在使用GetEnumerator循环编写相同内容时显式使用whilefor循环。

此外,您无需忽略sb.Append调用返回的值 - 您只需将其作为整体结果返回(因为它是 StringBuilder )。这实际上会使代码更有效(因为它启用了尾调用优化)。最后一点,ToString中不需要sb.Append(..),因为Append方法已超载,适用于所有标准类型。

所以经过一些简化之后,你可以得到这样的东西(它不是真的使用depth参数,但我想你以后想要用它来):

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) 

答案 1 :(得分:6)

以下是我如何使用它...

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

答案 2 :(得分:1)

在您的示例中,enumer.Current是包含PropertyInfo的对象。这意味着currObj始终是PropertyInfo对象,并且始终对应于match语句中的最后一个case。

由于您对属性的的类型感兴趣,因此您需要调用PropertyInfo的GetValue()方法以获取属性的实际值(如在ChaosPandion的回答中。

由于Enumerator将其值作为对象返回,因此您还需要先将enum.current转换为PropertyInfo,然后才能访问GetValue。

尝试替换

let currObj = (enumer.Current : obj)

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

通过此更改,我可以让您的代码工作(在FSI中):

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

答案 3 :(得分:0)

你想要这样的东西。

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

这来自Array类的MSDN文档:

  

在.NET Framework 2.0版中,   Array类实现了   System.Collections.Generic.IList,   了System.Collections.Generic.ICollection,   和   System.Collections.Generic.IEnumerable   通用接口。该   实现被提供给数组   在运行时,因此不是   对文档构建可见   工具。结果,通用   接口不会出现在   Array的声明语法   上课,并没有参考   接口成员的主题   只能通过将数组转换为   通用接口类型(显式   接口实现)。钥匙   当你施展时要注意的事情   数组到这些接口之一是   添加,插入或的成员   删除元素抛出   NotSupportedException异常。

答案 4 :(得分:0)

您确定程序的行为不符合预期吗?调试器跨度并不总是可靠的。