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

时间:2010-07-04 07:25:36

标签: f# pattern-matching

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

type MasterKey = | HeadContent | HeaderContent | MainContent | FooterContent
let MasterTemplate : Template<MasterKeys> = ...

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

   match result with
   | :? foo -> ...
   | :? bar -> ...
   | :? Template<'a> -> ...

有什么想法吗?

2 个答案:

答案 0 :(得分:6)

不幸的是,没有办法做得很好。如果您可以控制Template<'T>类型,那么最好的选择是创建非通用接口(例如ITemplate)并在Template<'T>类型中实现该接口。然后你可以检查界面:

| :? ITemplate as t -> ...

如果情况并非如此,那么你唯一的选择是使用一些反射魔法。您可以实现在类型为某个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

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

match result with
| GenericTemplate tys ->
    // ...

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

 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

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

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

答案 1 :(得分:1)

根据它使用的'key的类型,你能否使整个视图引擎类具有通用性? Indivudual项目需要从您的视图引擎类继承并指定该过程中的键类型。