I have a frustrating problem. I'm building a view engine in ASP.NET MVC and are implementing the interface IViewEngine. In one of the methods I'm trying to dynamically figure out the type of the result of a view. Sometimes the result is a template (with the type Template<'key>). The keys are used to target a placeholder in the template, and the idea is to use a discriminated union, potentially unique for each web site. It could look like this:
type MasterKey = | HeadContent | HeaderContent | MainContent | FooterContent
let MasterTemplate : Template<MasterKeys> = ...
Now, the problem is this: since I'm implementing an interface, I have no control over the method signature. Since I can not add a generic type parameter, the 'a will be converted to an obj and the Template will not be a match below:
match result with
| :? foo -> ...
| :? bar -> ...
| :? Template<'a> -> ...
Any ideas?
Unfortunately, there is no way to do this nicely. If you have control over the Template<'T> type, then the best option is to create a non-generic interface (e.g. ITemplate) and implement that in the Template<'T> type. Then you can just check for the interface:
| :? ITemplate as t -> ...
If that's not the case, then your only option is to use some reflection magic. You can implement an active pattern that matches when the type is some Template<'T> value and returns a list of types (System.Type objects) that were used as generic arguments. In your pseudo-code, you wanted to get this as the generic type parameter 'a - it isn't possible to get that as compile-time type parameter, but you can get that as runtime type information:
let (|GenericTemplate|_|) l =
let lty = typeof<list<int>>.GetGenericTypeDefinition()
let aty = l.GetType()
if aty.IsGenericType && aty.GetGenericTypeDefinition() = lty then
Some(aty.GetGenericArguments())
else
None
Now you can write the following pattern matching code:
match result with
| GenericTemplate tys ->
// ...
The last problem is - how can you use this runtime type information to run some generic code. The best option I can think of is to invoke a generic method (or a function) using reflection - then you can specify the runtime type information as a generic parameter and so the code can be generic. The simplest option is to invoke static member of a type:
type TemplateHandler =
static member Handle<'T>(arg:Template<'T>) =
// This is a standard generic method that will be
// called from the pattern matching - you can write generic
// body of the case here...
"aaa"
| :? GenericTemplate tys ->
// Invoke generic method dynamically using reflection
let gmet = typeof<TemplateHandler>.GetMethod("Handle").MakeGenericMethod(tys)
gmet.Invoke(null, [| result |]) :?> string // Cast the result to some type
The key idea is that you move body of a pattern matching (which cannot have generic type parameters) into a method (that can have generic type parameters) and run the method dynamically using reflection.
You could change the code to use let function instead of static member as well - it is only slightly more difficult to find the function using reflection.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With