Creating F# collections via reflection

During the course of a recent project I needed to instantiate some F# types via reflection. This is a well documented and fairly simple process for C#, but F#’s immutable collections pose an interesting challenge.

In this post I will describe the process of creating and populating an F# list using reflection, as well as an F# sequence for good measure.

Note that it is possible to create generic versions of the functions in this post, but when you’re working with reflection you are normally working with Type and Object rather than types you know about at compile type.

Creating a generic type

Both the list and sequence type are generic in F# (Microsoft.FSharp.Collections.List and System.Collections.Generic.IEnumerable respectively), so we will need a way of creating new generic types from the collection and item types.

Luckily this is fairly straight forward.

1 let makeGenericType (baseType : Type) (types : Type list) = 
2 
3   if (not baseType.IsGenericTypeDefinition) then
4     invalidArg "baseType" "baseType must be a generic type definition."
5 
6   baseType.MakeGenericType (
7     types
8     |> List.toArray
9   )

We can use this function to create generic types from a base type and type arguments. For example, here is how to create the type of int option:

1 let intOptionType = 
2   makeGenericType
3   <| typedefof<Option<_>>
4   <| [ typeof<Int32> ]

Note the use of typedefof rather than typeof. typedefof gives you a generic type definition (e.g. Option) while typeof will “fill in the blanks” and give you a full type definition (e.g. Option).

Creating a sequence

Now that we have a way of creating our collection’s type we can go about actually creating and populating it. F#’s sequence is actually just System.Collections.IEnumerable so we can use any type implementing that interface as a sequence – so let’s keep it simple and use List.

In order to create a sequence and populate it with some items we need to:

Create the desired sequence type, e.g. List. Create an instance of our sequence type using the default constructor. Add the items to the sequence. As List is mutable this is fairly straight forward.

 1 let makeSeqOf itemType (items : obj list) = 
 2 
 3         let seqType = 
 4             makeGenericType
 5             <| typedefof<List<_>>
 6             <| [ itemType; ]
 7 
 8         let sequence = 
 9             Activator.CreateInstance seqType
10 
11         let add = 
12 
13             let addMethod = 
14                 seqType.GetMethod ("Add")           
15             
16             fun item ->
17                 addMethod.Invoke (sequence, [| item; |]) 
18                 |> ignore
19 
20         items
21         |> List.iter add
22 
23         sequence

Creating a list

Creating an F# list follows the same basic principles but whereas any IEnumerable can be used for a sequence, meaning we can use the mutable List, the same is not true for lists.

The F# list is of type Microsoft.FSharp.Collections.List and it is immutable, which makes our job a little bit more complicated:

Create the desired generic type. Get a reference to the empty list for that type. Fold the items into the list, creating an extended list each time.

 1 let makeListOf itemType (items : obj list)  = 
 2 
 3         let listType = 
 4             makeGenericType 
 5             <| typedefof<Microsoft.FSharp.Collections.List<_>> 
 6             <| [ itemType; ]
 7 
 8         let add = 
 9 
10             let cons = 
11                 listType.GetMethod ("Cons")
12             
13             fun item list ->
14                 cons.Invoke (null, [| item; list; |])                
15 
16         let list = 
17 
18             let empty = 
19                 listType.GetProperty ("Empty")
20 
21             empty.GetValue (null)
22 
23         list
24         |> List.foldBack add items

F#’s list type has no default constructor, instead we need to get a reference to the empty list (the equivalent of List.empty or []). Empty is a static property on the list type so we just need to get its value using GetValue.

In order to add items our list we need to use the cons function (more commonly seen as ::) which creates a new list by prepending an item to the head of an existing list. Cons is a static method on the list type so we call it using Invoke and pass it the item to be added and the existing list, in that order (analogous to head :: tail).

By using foldBack we maintain the list order (because items are added in reverse order and cons prepends to the list rather than appending).

Source code

The code for the three functions described in this post, as well as various others, are available in my HandyFS project on GitHub (MIT licence).

Comments