How to cast an object to a list of generic type in F#
In the following snippet my intention is to convert a System.Object (which could be an FSharpList) to a list of whatever generic type it is holding.
match o with
开发者_如何学C | :? list<_> -> addChildList(o :?> list<_>)
| _ -> addChild(o)
Unfortunately only list<obj>
is ever matched as a list. I would like list<Foo>
to also be matched as a list.
For some context, I am trying to traverse an object structure by reflection in order to build a TreeView of the class and its children. Consider the following class:
type Entity = {
Transform : Matrix
Components : obj list
Children : Entity list
}
I would like to build a tree that shows me all the classes that is contained in the entity. Through reflection, I can obtain all the properties of an object and also their values (The value is important, since I want to display the different elements in a list with the Name property of the element if it has one):
let o = propertyInfo.GetValue(obj, null)
This value could be a list of some type, but the value return is just a System.Object I run into problems when trying to convert this object to a list. I am forced to do the following:
match o with
| :? list<obj> -> addChildList(o :?> list<obj>)
| :? list<Entity> -> addChildList(o :?> list<Entity>)
| _ -> addChild(o)
Here I have to specify exactly the type that I am trying to convert to.
I would really like to write this: match o with
| :? list<_> -> addChildList(o :?> list<_>)
| _ -> addChild(o)
Unfortunately this only ever matches on list< obj >
Unfortunately, there's no easy way to do what you want. Type tests can only be used with specific types, and even if the type test passed, the conversion operator :?>
also only works to cast expressions to specific types so the right hand side of your match wouldn't do what you want anyway. You can partially work around this issue using an active pattern:
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
This active pattern can be used as follows:
match o.GetType() with
| GenericType <@ typedefof<list<_>> @> [|t|] -> addChildListUntyped(t,o)
| _ -> addChild(o)
where you've created a variation of addChildList
which takes a type t
and an object o
(with runtime type list<t>
) instead of taking a generic list.
This is a bit clunky, but I can't think of a cleaner solution.
It turns out that either list<'a>
or array<'a>
can be matched as seq<obj>
match o with
| :? seq<obj> -> addChildCollection(o :?> seq<obj>)
| _ -> addChild(o)
I don't really care that it is a list. As long as I can iterate over it.
精彩评论