2010-01-26 17 views
6

Nel seguente snippet, la mia intenzione è quella di convertire un oggetto System.Object (che potrebbe essere un elenco FSharp) in un elenco di qualsiasi tipo generico in esso contenuto.Come trasmettere un oggetto a un elenco di tipo generico in F #

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

Purtroppo solo list<obj> è sempre abbinato come una lista. Vorrei che lo list<Foo> corrispondesse anche a un elenco.

Per alcuni contesti, sto cercando di attraversare una struttura di oggetti per riflessione al fine di costruire un TreeView della classe e dei suoi figli. Considerare la seguente classe:

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

Vorrei costruire un albero che mostri tutte le classi contenute nell'entità. Attraverso la riflessione, posso ottenere tutte le proprietà di un oggetto e anche i loro valori (Il valore è importante, dal momento che voglio visualizzare i diversi elementi in una lista con la proprietà Name dell'elemento se ne ha uno):

Questo valore potrebbe essere un elenco di un tipo, ma il valore restituito è solo un System.Object Si verificano problemi durante il tentativo di convertire questo oggetto in un elenco. Sono costretto a fare quanto segue:

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

Qui devo specificare esattamente il tipo che sto cercando di convertire.
Mi piacerebbe davvero scrivere questo:

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

Purtroppo questo corrisponde sempre e solo su list<obj>

+2

Hai davvero bisogno di un elenco digitato? Mi sembra che l'abbinamento di "IEnumerable" sia sufficiente. –

risposta

1

Si scopre che sia list<'a> o array<'a> può essere abbinato come seq<obj>

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

Non mi interessa che si tratta di una lista. Finché riesco a iterare su di esso.

+1

Questo dovrebbe funzionare su .NET 4.0, ma non funzionerà con le versioni precedenti come 'seq <'a>' non è contrassegnato come covariante. Inoltre, tieni presente che ciò funzionerà solo su elenchi o array contenenti tipi di riferimento (ad es.un 'elenco ' può essere trattato come un 'seq ', ma un 'elenco ' non può). – kvb

+2

Inoltre, penso che sarebbe leggermente più pulito fare il pattern match come '| :? seq as s -> addChildCollection (s) 'in modo da non avere un downcast esplicito. – kvb

+0

Questo piccolo trucco mi ha appena salvato il bacon quando avevo bisogno di trattare l'Elenco <'a> e un IEnumerable <'a> in modo omogeneo. Grazie! –

5

Purtroppo, non c'è un modo semplice per fare quello che vuoi. I test di tipo possono essere utilizzati solo con tipi specifici, e anche se il test del tipo è passato, l'operatore di conversione :?> funziona anche solo per trasmettere espressioni a tipi specifici in modo che il lato destro della corrispondenza non faccia ciò che vuoi comunque. Si può parzialmente ovviare a questo problema utilizzando un modello attivo:

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 

Questo modello attivo può essere utilizzato come segue:

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

in cui hai creato una variante di addChildList che prende un tipo t e un oggetto o (con il tipo di runtime list<t>) anziché un elenco generico.

Questo è un po 'goffo, ma non riesco a pensare a una soluzione più pulita.

Problemi correlati