Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get underlying type for a sequence (array, list, seq)

Tags:

.net

f#

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,

  • name: String
  • cars: IStructuralEquatable[]
  • names: FSharpList<String>
  • others: IEnumerable<String>
  • others2: List<String>

How should extractTypeInfo be updated so that the result will have the following info

  • name: String
  • cars: Car
  • names: String
  • others: String
  • others2: String
like image 738
Richard Avatar asked Dec 11 '22 12:12

Richard


2 Answers

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
like image 90
TheInnerLight Avatar answered Dec 15 '22 16:12

TheInnerLight


@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:

  1. While F# type system makes arrays look like proper generic types, this is not how they appear to .NET reflection API. Given an array type, you'd get 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.
  2. You're looking at the generic type argument to the 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.

like image 33
scrwtp Avatar answered Dec 15 '22 14:12

scrwtp