F#类型模式匹配

19

我正在尝试递归打印出一个对象及其子类型的所有属性等。我的对象模型如下...

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的类型为字符串、整数、浮点数等,但它总是跳转到底部的默认情况。 有任何想法为什么会发生这种情况?
5个回答

19

正如其他人指出的那样,您需要调用GetValue成员来获取属性的值。您实现的迭代遍历了PropertyInfo对象,它们是属性的“描述符”,而不是实际值。不过,我不太明白为什么在同样的情况下您要显式地使用GetEnumeratorwhile循环,而不是使用for循环。

此外,在sb.Append调用中你不需要忽略返回的值-你可以将其简单地作为总体结果返回(因为它是the StringBuilder)。这实际上会使代码更有效率(因为它启用了尾递归优化)。最后,你不需要在sb.Append(..)中使用ToString,因为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) 

我一直在使用 |> ignore 并返回 ()。 - PhilBrown

8

以下是我是如何让它起作用的...

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

3
在你的例子中,enumer.Current是一个包含PropertyInfo的对象。这意味着currObj始终是一个PropertyInfo对象,并且始终对应于您匹配语句中的最后一个情况。
由于您对属性的值类型感兴趣,因此需要调用PropertyInfo的GetValue()方法才能获取到属性的实际值(如ChaosPandion所述)。
由于枚举器将其值作为对象返回,因此在访问GetValue之前还需要将enum.current强制转换为PropertyInfo。
尝试替换
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."

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

这是来自MSDN文档的Array类:

在.NET Framework 2.0版本中,Array类实现了System.Collections.Generic.IList、System.Collections.Generic.ICollection和System.Collections.Generic.IEnumerable泛型接口。这些实现是在运行时提供给数组的,因此对于文档构建工具而言是不可见的。因此,在Array类的声明语法中不会出现泛型接口,并且没有关于只能通过将数组强制转换为泛型接口类型(显式接口实现)才能访问的接口成员的参考主题。当您将数组强制转换为其中一个接口时,需要注意的关键事项是添加、插入或删除元素的成员会抛出NotSupportedException异常。


编译器告诉我: 字段、构造函数或成员“GetValue”未定义。 - PhilBrown
@philbrowndotcom - 我认为你需要进行类型转换。 - ChaosPandion

0

你确定程序没有按预期运行吗?调试器跨度并不总是可靠的。


问题在于 enumer.Current.GetValue 不是真正的值,而是 PropertyInfo 对象。这里有一些抽象概念让我感到困惑。 - PhilBrown
你在使用GetType/GetProperties等API时使用了哪个?也许它的文档可以解释这个问题? - Brian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接