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

Joh*_*han 7 generics f# casting list

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

    match o with
    | :? list<_>              -> addChildList(o :?> list<_>)
    | _                       -> addChild(o)
Run Code Online (Sandbox Code Playgroud)

不幸的是,只有list<obj>匹配列表.我也想list<Foo>作为一个列表进行匹配.

对于某些上下文,我试图通过反射遍历对象结构,以便构建类及其子类的TreeView.考虑以下课程:

type Entity = {
    Transform   : Matrix
    Components  : obj list
    Children    : Entity list
}
Run Code Online (Sandbox Code Playgroud)

我想构建一个树,向我展示实体中包含的所有类.通过反射,我可以获得对象的所有属性及其值(值很重要,因为我想在列表中显示元素的Name属性,如果有的话):

        let o = propertyInfo.GetValue(obj, null)
Run Code Online (Sandbox Code Playgroud)

此值可能是某种类型的列表,但值返回只是一个System.Object我在尝试将此对象转换为列表时遇到问题.我被迫做以下事情:

        match o with
        | :? list<obj>              -> addChildList(o :?> list<obj>)
        | :? list<Entity>           -> addChildList(o :?> list<Entity>)
        | _                         -> addChild(o)
Run Code Online (Sandbox Code Playgroud)

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

        match o with
        | :? list<_>              -> addChildList(o :?> list<_>)
        | _                       -> addChild(o)
Run Code Online (Sandbox Code Playgroud)

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

kvb*_*kvb 5

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

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
Run Code Online (Sandbox Code Playgroud)

此活动模式可以使用如下:

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

您创建了一个变体,addChildList其中包含一个类型t和一个对象o(具有运行时类型list<t>),而不是采用通用列表.

这有点笨拙,但我想不出更清洁的解决方案.


Joh*_*han 2

事实证明,list<'a>orarray<'a>可以匹配为seq<obj>

    match o with
    | :? seq<obj> -> addChildCollection(o :?> seq<obj>)
    | _           -> addChild(o)
Run Code Online (Sandbox Code Playgroud)

我真的不在乎它是一个列表。只要我能迭代它。

  • 这应该适用于 .NET 4.0,但不适用于以前的版本,因为 `seq&lt;'a&gt;` 未标记为协变。另外,请记住,这仅适用于包含引用类型的列表或数组(例如,“list&lt;string&gt;”可以被视为“seq&lt;obj&gt;”,但“list&lt;int&gt;”则不能) 。 (2认同)
  • 另外,我认为像“|”这样进行模式匹配会稍微干净一些。:? seq&lt;obj&gt; as s -&gt; addChildCollection(s)` 所以你没有显式的向下转型。 (2认同)