2010-01-26 12 views
6

W poniższym ujęciu moim zamiarem jest przekonwertowanie obiektu System.Object (który może być FSharpList) na listę dowolnego typowego dla niego typu.Jak rzutować obiekt na listę typu ogólnego w F #

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

Niestety tylko list<obj> jest zawsze dopasowana w postaci listy. Chciałbym, aby list<Foo> również zostało dopasowane jako lista.

Dla pewnego kontekstu próbuję przejść przez strukturę obiektu poprzez odbicie, aby zbudować TreeView klasy i jej dzieci. Rozważmy następującą klasę:

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

Chciałbym zbudować drzewo, które pokaże mi wszystkie klasy zawarte w encji. Dzięki odbiciu mogę uzyskać wszystkie właściwości obiektu, a także ich wartości (Wartość jest ważna, ponieważ chcę wyświetlić różne elementy na liście z właściwością Nazwa elementu, jeśli ma on):

 let o = propertyInfo.GetValue(obj, null) 

Ta wartość może być listą pewnego typu, ale zwracana wartość to tylko System.Object Występują problemy podczas próby przekształcenia tego obiektu na listę. Jestem zmuszony wykonać następujące czynności:

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

Tutaj muszę podać dokładnie ten typ, który próbuję przekonwertować.
naprawdę chciałbym napisać to:

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

Niestety to tylko kiedykolwiek mecze na list<obj>

+2

Czy naprawdę potrzebujesz wpisanej listy? Wydaje mi się, że dopasowanie "IEnumerable" wystarczy. –

Odpowiedz

1

Okazuje się, że albo list<'a> lub array<'a> można dopasować jak seq<obj>

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

I naprawdę nie obchodzi, że jest to lista. Dopóki mogę to powtórzyć.

+1

To powinno działać na .NET 4.0, ale nie będzie działać w poprzednich wersjach jako 'seq <'a>' nie jest oznaczony jako kowariancja. Pamiętaj też, że działa to tylko na listach lub tablicach zawierających typy referencyjne (np.'lista ' może być traktowana jako 'seq ', ale 'lista ' nie może). – kvb

+2

Sądzę też, że byłoby nieco czystsze wykonywanie dopasowań wzorców takich jak '| :? seq jako s -> addChildCollection (s) ', więc nie masz wyraźnego downcasta. – kvb

+0

Ta mała sztuczka właśnie uratowała mi boczek, kiedy potrzebowałem jednorodnie traktować listę <'a> i IEnumerable <'a>. Dzięki! –

5

Niestety, nie ma łatwego sposobu, aby robić to, co chcesz. Testy typów mogą być używane tylko z określonymi typami, a nawet jeśli test typu minął, operator konwersji :?> działa również tylko w celu rzutowania wyrażeń na określone typy, więc prawostronna strona twojego meczu i tak nie zrobiłaby tego, czego chcesz. Można częściowo obejść ten problem za pomocą aktywnego wzoru:

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 

Ten aktywny wzór może być stosowany w sposób następujący:

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

gdzie utworzeniu odmianą addChildList który trwa i typ t obiekt o (z modułem wykonawczym typu list<t>) zamiast pobierania ogólnej listy.

To jest trochę niezgrabne, ale nie mogę myśleć o czystszym rozwiązaniu.

Powiązane problemy