在实现接口的非泛型方法中对泛型的模式匹配

Joh*_*son 2 f# pattern-matching

我有一个令人沮丧的问题.我正在ASP.NET MVC中构建一个视图引擎,并正在实现接口IViewEngine.在其中一种方法中,我试图动态地找出视图结果的类型.有时结果是模板(类型为Template <'key>).密钥用于在模板中定位占位符,其目的是使用区分联合,可能对每个网站都是唯一的.它可能看起来像这样:

type MasterKey = | HeadContent | HeaderContent | MainContent | FooterContent
let MasterTemplate : Template<MasterKeys> = ...
Run Code Online (Sandbox Code Playgroud)

现在,问题是这样的:因为我正在实现一个接口,所以我无法控制方法签名.由于我无法添加泛型类型参数,因此'a'将转换为obj,而下面的模板将不匹配:

   match result with
   | :? foo -> ...
   | :? bar -> ...
   | :? Template<'a> -> ...
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

Tom*_*cek 6

不幸的是,没有办法很好地做到这一点.如果您可以控制Template<'T>类型,那么最好的选择是创建一个非泛型接口(例如ITemplate)并在Template<'T>类型中实现它.然后你可以检查界面:

| :? ITemplate as t -> ...
Run Code Online (Sandbox Code Playgroud)

如果情况并非如此,那么你唯一的选择是使用一些反射魔法.您可以实现一个活动模式,该模式在类型为某个Template<'T>值时匹配,并返回System.Type用作泛型参数的类型(对象)列表.在您的伪代码中,您希望将此作为泛型类型参数'a- 不可能将其作为编译时类型参数获取,但您可以将其作为运行时类型信息获取:

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
Run Code Online (Sandbox Code Playgroud)

现在您可以编写以下模式匹配代码:

match result with
| GenericTemplate tys ->
    // ...
Run Code Online (Sandbox Code Playgroud)

最后一个问题是 - 如何使用此运行时类型信息来运行一些通用代码.我能想到的最佳选择是使用反射调用泛型方法(或函数) - 然后您可以将运行时类型信息指定为通用参数,因此代码可以是通用的.最简单的选择是调用类型的静态成员:

 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
Run Code Online (Sandbox Code Playgroud)

关键思想是将模式匹配的主体(不能具有泛型类型参数)移动到方法(可以具有泛型类型参数)中,并使用反射动态运行方法.

您可以将代码更改为使用let函数而不是使用函数static member- 使用反射查找函数稍微困难一些.