f# 与类型进行模式匹配

发布于 2024-09-04 19:22:46 字数 1477 浏览 8 评论 0原文

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

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 等,但它总是跳转到底部的默认情况。知道为什么会发生这种情况吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

你如我软肋 2024-09-11 19:22:46

正如其他人指出的那样,您需要调用 GetValue 成员来获取属性的值 - 您实现的迭代会迭代 PropertyInfo 对象,这些对象是“属性的描述符”财产” - 不是实际价值。但是,我不太明白为什么在可以使用 for 循环编写相同的内容时显式使用 GetEnumeratorwhile 循环。

另外,您不需要忽略 sb.Append 调用返回的值 - 您只需将其作为总体结果返回(因为它是 StringBuilder)。这实际上会使代码更加高效(因为它可以实现尾部调用优化)。最后一点,您不需要 sb.Append(..) 中的 ToString,因为 Append 方法已重载并且适用于所有标准类型。

因此,经过一些简化后,您可以得到类似这样的结果(它并不是真正使用 深度 参数,但我猜您想在以后使用它):

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) 

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) 
断桥再见 2024-09-11 19:22:46

这是我如何让它工作的......

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

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
                            ...
家住魔仙堡 2024-09-11 19:22:46

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

由于您对属性的的类型感兴趣,因此您需要调用 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."

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."
友欢 2024-09-11 19:22:46

您确定该程序没有按预期运行吗?调试器范围并不总是可靠的。

Are you sure the program is not behaving as expected? The debugger spans are not always reliable.

写给空气的情书 2024-09-11 19:22:46

你想要这样的东西。

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 版中,
数组类实现了
System.Collections.Generic.IList,
System.Collections.Generic.ICollection,

System.Collections.Generic.IEnumerable
通用接口。这
提供给数组的实现
在运行时,因此不是
对文档构建可见
工具。结果,通用的
界面中没有出现
数组的声明语法
类,并且没有参考
接口成员的主题是
只能通过将数组转换为
通用接口类型(显式
接口实现)。关键
当你施放时要注意的事情
这些接口之一的数组是
添加、插入或
删除元素抛出
NotSupportedException。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文