2010-01-26 10 views
6

En el siguiente fragmento de código, mi intención es convertir un System.Object (que podría ser una FSharpList) en una lista del tipo genérico que tenga.Cómo convertir un objeto en una lista de tipo genérico en F #

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

Desgraciadamente, sólo se list<obj> jamás igualado en forma de lista. Me gustaría que list<Foo> también coincida como una lista.

Por algún contexto, estoy tratando de atravesar una estructura de objeto por reflexión para construir una TreeView de la clase y sus elementos secundarios. Considere la siguiente clase:

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

Me gustaría construir un árbol que me muestre todas las clases que contiene la entidad. A través de la reflexión, puedo obtener todas las propiedades de un objeto y sus valores (El valor es importante, ya que quiero mostrar los diferentes elementos en una lista con la propiedad Nombre del elemento si tiene uno):

 let o = propertyInfo.GetValue(obj, null) 

Este valor podría ser una lista de algún tipo, pero el valor devuelto es solo un System.Object Me encuentro con problemas al tratar de convertir este objeto a una lista. Estoy obligado a hacer lo siguiente:

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

Aquí tengo que especificar exactamente el tipo que estoy tratando de convertir.
Realmente me gustaría que escribir esto:

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

Desafortunadamente esto coincide con sólo alguna vez en list<obj>

+2

¿Realmente necesita una lista mecanografiada? Me parece que emparejar 'IEnumerable' sería suficiente. –

Respuesta

1

Resulta que sea list<'a> o array<'a> puede ser igualado como seq<obj>

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

Realmente no importa que se trata de una lista. Mientras pueda iterar sobre eso.

+1

Eso debería funcionar en .NET 4.0, pero no funcionará en versiones anteriores ya que 'seq <'a>' no está marcado como covariante. Además, tenga en cuenta que esto solo funcionará en listas o matrices que contengan tipos de referencia (p.una 'lista ' se puede tratar como 'seq ', pero una 'lista 'no puede). – kvb

+2

Además, creo que sería un poco más limpio hacer la coincidencia de patrón como '| :? seq como s -> addChildCollection (s) 'por lo que no tiene un downcast explícito. – kvb

+0

Este pequeño truco acaba de salvar mi tocino cuando necesitaba tratar la Lista <'a> y un IEnumerable <'a> homogéneamente. ¡Gracias! –

5

Desafortunadamente, no hay manera fácil de hacer lo que quiera. Las pruebas de tipo solo se pueden usar con tipos específicos, e incluso si se pasó la prueba de tipo, el operador de conversión :?> también solo funciona para enviar expresiones a tipos específicos, por lo que el lado derecho de la coincidencia no hará lo que quiera de todos modos. Puede trabajar parcialmente este problema usando un patrón activo:

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 

Este patrón activo puede ser utilizado de la siguiente manera:

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

donde se ha creado una variación de addChildList que tiene un tipo y t un objeto o (con tipo de tiempo de ejecución list<t>) en lugar de tomar una lista genérica.

Esto es un poco torpe, pero no se me ocurre una solución más limpia.

Cuestiones relacionadas