Reputation: 145
Given a type
with various properties, how does one extract the type of property when the property is array
, list
or seq
?
Sample code below
type Car = { name: string }
type CarStore =
{ name: string
cars: Car[]
names: string list
others: string seq
others2: ResizeArray<string> }
let peek x =
Dump x
x
let extractTypeInfo name (typ:Type) =
let isEnumerable = true //todo: true for list, array, seq, etc
let underlyingType = typ //todo: if isEnumerable = true, get actual type
isEnumerable, underlyingType
typeof<CarStore>.GetProperties()
|> peek
|> Seq.map (fun p -> extractTypeInfo p.Name p.PropertyType)
|> Dump
Executing the above gives the following properties,
String
IStructuralEquatable[]
FSharpList<String>
IEnumerable<String>
List<String>
How should extractTypeInfo
be updated so that the result will have the following info
String
Car
String
String
String
Upvotes: 4
Views: 120
Reputation: 13577
@TheInnerLight's answer gives a good working solution, but as usual with reflection, you should be very conscious about what you really want to see and what you are really looking at.
I feel there are two subtle points worth noting here:
typ.IsGenericType
equal to false
, but typ.IsArray
equal to true
, and you can get the type parameter with typ.GetElementType()
. This is for historic reasons, since the reflection API predates .NET generics.IEnumerable<'a>
interface implementation on the type - and only at that. What this means is that while the generic type arguments to the type and to the interface are the same if you only consider lists, seqs and arrays, in the general case it's not a given. You can have a generic type instantiated with different type arguments than its IEnumerable<'a>
implementation (this is a case with maps and dictionaries), or with multiple implementations of IEnumerable<'a>
(in which case this solution will pick up the first one it finds).These may not be things you might immediately find important for your solution, but you might stumble on them in time.
Upvotes: 5
Reputation: 12184
I'd be inclined to do something like this:
let extractTypeInfo (typ:Type) =
typ.GetInterfaces()
|> Array.tryFind (fun iFace ->
iFace.IsGenericType && iFace.GetGenericTypeDefinition() = typedefof<seq<_>>)
|> Option.map (fun iFace -> iFace.GetGenericArguments().[0])
This approach captures whether or not the type is a seq<'T>
by returning Some
or None
and the type of 'T
within the result of the Some
case.
Some examples in FSI:
extractTypeInfo typeof<float array>;; val it : System.Type option = Some System.Double ... extractTypeInfo typeof<string list>;; val it : System.Type option = Some System.String ... extractTypeInfo typeof<int>;; val it : System.Type option = None
Upvotes: 8