2010-01-26 93 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>)而不是採用通用列表。

這有點笨重,但我想不出一個更清潔的解決方案。

相關問題