While I was visiting Boston earlier in the year I had the misfortune of kicking myself in the teeth with reflection. It’s something all programmers inevitably go through with reflection API’s as they are inherently untyped, a simple typo can leave you tearing out your hair or punching through your monitor! Yeah there’s things the horizon that will help namely the nameof expression in C#6 which should help in some areas, that’s if your willing to pay the price of using C#, but I wont go into that here :-). In F# we can leverage Type Providers fairly easily to wrap API usages in cases that we are interested in, or even create a general usage with a little more effort.
Using the Type Provider
In usage it will look like this vs the usual reflection API:
//traditional reflection using untyped method let tt = typeof<DateTime> let meth = tt.GetMethod("Add") let result = meth.Invoke(DateTime.Now, [|TimeSpan.FromDays(1.)|]) //using the type provider to provide a little safety net type rt = TypedReflection.Reflection< "System.DateTime", "AddSeconds"> let result = rt.AddSeconds(DateTime.Now, 1.)
If you make a mistake the compiler will tell you and you will be forces to fix the typo or add namespace prefixes etc. You also get intellisense.autocompletion on usage and you can give actual parameters rather than arrays of loose objects etc.
First of all I’m just going to leave the code here, and then talk through it below:
[<TypeProvider>] type public ReflectionTypeProvider(config : TypeProviderConfig) as this = inherit TypeProviderForNamespaces() let assembly = Assembly.GetExecutingAssembly() let nameSpace = this.GetType().Namespace let providerType = ProvidedTypeDefinition(assembly, nameSpace, "Reflection", Some typeof<obj>, IsErased = true, HideObjectMethods = true) let buildReflection typeName (parameters : obj) = let reflectionType = string parameters. let methodName = string parameters. let theType = Type.GetType(reflectionType, true) let meth = theType.GetMethod(methodName) if meth = null then failwith "No such method!" let wrapper = ProvidedTypeDefinition(assembly, nameSpace, typeName, Some (typeof<obj>), HideObjectMethods = true ) let parameterInfoToProvidedParameter (meth:MethodInfo) = let pi = meth.GetParameters() let instance = ProvidedParameter("instance", meth.ReflectedType) let parameters = pi |> Seq.map (fun p -> ProvidedParameter(p.Name, p.ParameterType) ) |> Seq.toList instance :: parameters let reflectionWrapper = ProvidedMethod (meth.Name, parameterInfoToProvidedParameter meth, meth.ReturnType, IsStaticMethod = true, InvokeCode = function | instance :: parameters -> try Expr.Call (instance, meth, parameters) with exn -> failwith "Error creating Invoke code." | _ -> failwith "Error: unexpected number of parameters" ) wrapper.AddMember reflectionWrapper wrapper do providerType.DefineStaticParameters ([ ProvidedStaticParameter("Type", typeof<string>) ProvidedStaticParameter("Method", typeof<string>) ], buildReflection) this.AddNamespace (nameSpace, [ providerType ]) [<assembly:TypeProviderAssembly>] do()
Reading from the bottom up you can see the parameters that our Type Provider accepts are
Method, those a pretty self explanatory. You should also notice other boiler plate Type Provider code if you read my last ZipProvider post. The important part here is the
First of all on lines
12/13 we scrape of the configuration parameters
meth, we then do a quick check to ensure the type and method actually exist, if they don’t we raise an error on line
17 so the use can correct the code.
Next we create a variable named wrapper which wraps round the reflection API by creating a
ProvidedTypeDefinition on line
19. We now have two methods which we use to create our safe API,
The purpose of this is a mapping function from the reflection API’s untyped abstract form to our typed form that we use in the construction of the Provided methods. Essentially this is pretty simple, we get the parameters for the
MethodInfo which we are wrapping on line
23. The first parameter will be the instance of the reflected method will be working on, and the rest of the parameters will be those of the reflected method. To add those we loop over the parameters from the
MethodInfo and map then to
ProvidedProperties by using the
(Thinking about this we could do it slightly differently by adding a
ProvidedConstructor which could take the initial instance, this could be added fairly easily if we really needed it. )
The reflectionWrapper is where the magic happens, we create a
ProvidedMethod using the
MethodInfo’s name', we add the parameters by using the
parameterInfoToProvidedParameter function, and we also add the return type by using the
ReturnType parameter'. We can also take advantage of object initializers here to set
IsStaticMethod to true, and to add in the invoke code.
The invoke code uses the
function keyword which is really just a pattern match expression using only a single argument, here we use pattern matching on a list to extract the
head|tail arguments. If you remember the
parameterInfoToProvidedParameter function then you will know that it returns a list
instance :: parameters. We can now use the Quotations
Expr type with the
Call function and pass in our instance and parameters in directly (instance is the reflected methods instance type,
meth is the
MethodInfo we will be calling, parameters are the parameters the
Finally we just add the
reflectionWrapper to the
This is a fairly simple implementation but it could be beefed up quite easily into something a little more elaborate without too much trouble. If you use your imagination then there are numerous possibilities with Type Providers!
Reminds me of an old proverb:
If you have a problem ... if no one else can help ... and if you cant find an existing one ... maybe you can build ... a Type Provider.
Until next time!