2010-01-26 92 views
6

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

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

不幸的是只有list<obj>被匹配作为列表。我想list<Foo>也被列为匹配。

对于某些上下文,我试图通过反射来遍历对象结构,以便构建类和它的子类的TreeView。考虑以下类:

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

我想构建一个树,显示包含在实体中的所有类。 通过反射,我可以获得对象的所有属性以及它们的值(该值非常重要,因为我希望在元素的名称属性列表中显示不同元素(如果它具有一个元素):

 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>

+2

你真的需要打字清单吗?在我看来,匹配'IEnumerable'就足够了。 – 2010-01-26 17:44:45

回答

1

事实证明,无论是list<'a>array<'a>可以作为seq<obj>

match o with 
    | :? seq<obj> -> addChildCollection(o :?> seq<obj>) 
    | _   -> addChild(o) 

匹配我真的不关心它是一个列表。只要我可以迭代它。

+1

这应该在.NET 4.0上运行,但不适用于以前的版本,因为'seq <'a>'未标记为协变。另外请记住,这只适用于包含引用类型的列表或数组(例如'list '可以被视为'seq ',但列表'不能)。 – kvb 2010-03-30 22:14:46

+2

另外,我认为它会稍微更干净一些,比如'| | :? seq as s - > addChildCollection(s)'所以你没有明确的downcast。 – kvb 2010-03-30 22:16:21

+0

当我需要同时处理列表<'a>和IEnumerable <'a>时,这个小技巧只是保存了我的培根。谢谢! – 2012-03-25 22:22:53

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 

如下这种主动模式可以使用::可以使用主动模式部分解决此问题

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

,你所创建的addChildList这需要一个类型t和变化一个对象o(运行时类型为list<t>)而不是采用通用列表。

这有点笨重,但我想不出一个更清洁的解决方案。

相关问题