2010-01-26 2 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 
} 

Я хотел бы построить дерево, которое покажет мне все классы, которые содержатся в объекте. Через отражение я могу получить все свойства объекта, а также их значения (значение важно, так как я хочу отображать различные элементы в списке с свойством Name элемента, если он есть):

 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' было бы достаточно. –

ответ

1

Оказывается, что либо list<'a> или array<'a> может быть подобран в seq<obj>

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

Я действительно не волнует, что это список. Пока я могу перебирать его.

+1

Это должно работать на .NET 4.0, но не будет работать в предыдущих версиях, так как 'seq <'a>' не помечен как ковариантный. Кроме того, имейте в виду, что это будет работать только в списках или массивах, содержащих ссылочные типы (например,a 'list ' можно рассматривать как 'seq ', но 'list ' не может. – kvb

+2

Кроме того, я думаю, что было бы немного чище сделать совпадение с шаблоном, как '| :? seq as s -> addChildCollection (s) ', поэтому у вас нет явного downcast. – kvb

+0

Этот маленький трюк только что спас мой бекон, когда мне нужно было обрабатывать список <'a> и IEnumerable <'a> равномерно. Благодаря! –

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>) вместо того, чтобы брать общий список.

Это немного неуклюжий, но я не могу придумать более чистого решения.

Смежные вопросы