如何在 F# 中将对象转换为泛型类型列表

2024-05-05

在下面的代码片段中,我的目的是将 System.Object(可能是 FSharpList)转换为它所持有的任何泛型类型的列表。

    match o with
    | :? list<_>              -> addChildList(o :?> list<_>)
    | _                       -> addChild(o)

不幸的是只有list<obj>曾经作为列表进行匹配。我想list<Foo>也可以作为列表进行匹配。

对于某些上下文,我尝试通过反射遍历对象结构,以便构建该类及其子级的 TreeView。考虑下面的类:

type Entity = {
    Transform   : Matrix
    Components  : obj list
    Children    : Entity list
}

我想构建一棵树来显示实体中包含的所有类。 通过反射,我可以获得对象的所有属性及其值(该值很重要,因为我想使用该元素的 Name 属性(如果有)显示列表中的不同元素):

        let o = propertyInfo.GetValue(obj, null)

该值可以是某种类型的列表,但返回的值只是一个 System.Object 尝试将此对象转换为列表时遇到问题。我被迫执行以下操作:

        match o with
        | :? list<obj>              -> addChildList(o :?> list<obj>)
        | :? list<Entity>           -> addChildList(o :?> list<Entity>)
        | _                         -> addChild(o)

在这里我必须准确指定我要转换的类型。
我真的很想写这个:

        match o with
        | :? list<_>              -> addChildList(o :?> list<_>)
        | _                       -> addChild(o)

不幸的是,这只匹配list< obj >


不幸的是,没有简单的方法可以做你想做的事。类型测试只能用于特定类型,即使类型测试通过,转换运算符:?>也只能将表达式转换为特定类型,因此匹配的右侧无论如何都不会执行您想要的操作。您可以使用活动模式部分解决此问题:

open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns

let ( |GenericType|_| ) =
  (* methodinfo for typedefof<_> *)
  let tdo = 
    let (Call(None,t,[])) = <@ typedefof<_> @>
    t.GetGenericMethodDefinition()
  (* match type t against generic def g *)
  let rec tymatch t (g:Type) =
    if t = typeof<obj> then None
    elif g.IsInterface then
      let ints = if t.IsInterface then [|t|] else t.GetInterfaces()
      ints |> Seq.tryPick (fun t -> if (t.GetGenericTypeDefinition() = g) then Some(t.GetGenericArguments()) else None)
    elif t.IsGenericType && t.GetGenericTypeDefinition() = g then
      Some(t.GetGenericArguments())
    else
      tymatch (t.BaseType) g
  fun (e:Expr<Type>) (t:Type) ->
    match e with
    | Call(None,mi,[]) ->
        if (mi.GetGenericMethodDefinition() = tdo) then
          let [|ty|] = mi.GetGenericArguments()
          if ty.IsGenericType then
            let tydef = ty.GetGenericTypeDefinition()
            tymatch t tydef
          else None
        else
          None
    | _ -> None

该活动模式可按如下方式使用:

match o.GetType() with
| GenericType <@ typedefof<list<_>> @> [|t|] -> addChildListUntyped(t,o)
| _                                          -> addChild(o)

你创建了一个变体addChildList它需要一个类型t和一个物体o(运行时类型list<t>)而不是采用通用列表。

这有点笨拙,但我想不出更干净的解决方案。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 F# 中将对象转换为泛型类型列表 的相关文章