Reputation: 13
What is the best way to create an instance of System.Type representing an F# record or union at runtime? That is, I am looking for an equivalent of FSharpType.MakeTupleType for records and unions.
Just to clarify, I am not interested in creating an instance (i.e. FSharpValue.MakeRecord or FSharpValue.MakeUnion).
Upvotes: 1
Views: 592
Reputation: 6543
I am not aware of an equivalent to FSharpType.MakeTupleType for records and unions in the F# library.
One way to create record or union type like structures at runtime is to use Reflection.Emit. A record type is analogous to a sealed class and a union type is an abstract base class with sealed classes for each case.
For example the following function generates a minimal F# record type:
open System
open System.Reflection
open System.Reflection.Emit
let MakeRecord(typeName:string, fields:(string * Type)[]) =
let name = "GeneratedAssembly"
let domain = AppDomain.CurrentDomain
let assembly = domain.DefineDynamicAssembly(AssemblyName(name), AssemblyBuilderAccess.RunAndSave)
let name = "GeneratedModule"
let dm = assembly.DefineDynamicModule(name, name+".dll")
let attributes = TypeAttributes.Public ||| TypeAttributes.Class ||| TypeAttributes.Sealed
let typeBuilder = dm.DefineType(typeName, attributes)
let con = typeof<CompilationMappingAttribute>.GetConstructor([|typeof<SourceConstructFlags>|])
let customBuilder = CustomAttributeBuilder(con, [|SourceConstructFlags.RecordType|])
typeBuilder.SetCustomAttribute(customBuilder)
let makeField name t =
let attributes = FieldAttributes.Assembly
let fieldBuilder = typeBuilder.DefineField(name+"@", t, attributes)
let attributes = PropertyAttributes.None
let propertyBuilder = typeBuilder.DefineProperty(name, attributes, t, [||])
let customBuilder = CustomAttributeBuilder(con, [|SourceConstructFlags.Field|])
propertyBuilder.SetCustomAttribute(customBuilder)
let attributes = MethodAttributes.Public ||| MethodAttributes.HideBySig ||| MethodAttributes.SpecialName
let methodBuilder = typeBuilder.DefineMethod("get_"+name, attributes, t, [||])
let il = methodBuilder.GetILGenerator()
il.Emit(OpCodes.Ldarg_0)
il.Emit(OpCodes.Ldfld, fieldBuilder)
il.Emit(OpCodes.Ret)
propertyBuilder.SetGetMethod(methodBuilder)
fieldBuilder
let types = fields |> Array.map snd
let cb = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, types)
let il = cb.GetILGenerator()
il.Emit(OpCodes.Ldarg_0)
il.Emit(OpCodes.Call, typeof<obj>.GetConstructor(Type.EmptyTypes))
fields |> Array.iteri (fun i (name, t) ->
let paramName = name.Substring(0,1).ToLower()+name.Substring(1)
let param = cb.DefineParameter(i+1, ParameterAttributes.In, paramName)
let fieldBuilder = makeField name t
il.Emit(OpCodes.Ldarg_0)
il.Emit(OpCodes.Ldarg, param.Position)
il.Emit(OpCodes.Stfld, fieldBuilder)
)
il.Emit(OpCodes.Ret)
let t = typeBuilder.CreateType()
assembly.Save("GeneratedModule.dll")
t
let r = MakeRecord("MyRecord", [|"Alpha",typeof<int>;"Beta",typeof<string>|])
Note the expected interfaces for a Record type may also need to be generated, i.e. implementations of IEquatable, IStructuralEquatable, IComparable and IStructuralComparable are missing.
Update
Extension methods MakeTupleType and MakeUnionType based on the code sample above are now available in the open source Fil (F# to IL Compiler) project (alpha).
Upvotes: 2