开发者

Pattern matching on generics in a non-generic method implementing an interface

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 开发者_StackOverflowtemplate (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.


Can you make the whole view engine class generic, according to the type of 'key it uses? Indivudual projects will need to inherit from your view engine class and specify the type of key in the process.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜