我可以在MethodInfo对象上找到应用的泛型参数的位置吗?

时间:2016-11-25 15:47:53

标签: c# generics f# system.reflection

我需要找出应用函数的确切通用参数的位置 假设我有以下函数并输入

type Box<'a, 'b> = Box of 'a * 'b
let iToS<'a, 'b> (b : Box<'a, 'b>) : Box<string, 'b> =
    let (Box (i,v)) = b 
    Box ((sprintf "%A" i), v)

我有一个函数获取一个函数并返回相应的MethodInfo对象

let getMethodInfo f : MethodInfo = ...
let mi = getMethodInfo <@ f @>
//now query mi 

无论如何都要找出通用参数的应用位置?这样

let iToS<'a, 'b> (b : Box<'a, 'b>) : Box<string, 'b> =
          |   |            ^   ^                  ^
          |   |            |   |                  |
          -----------------    |                  |
              |                |                  |
              ------------------------------------

给定泛型函数(及其关联的MethodInfo对象)和(潜在的通用)函数参数和(潜在的通用)返回类型, 有没有办法找出函数的泛型参数是否应用于函数参数类型和/或函数返回类型如果其中任何一个也是通用类型?

2 个答案:

答案 0 :(得分:4)

您可以使用.NET反射来查找通用参数在参数和返回通用函数类型中的位置。

例如,让我们看一下Map.map的包装器:

module Test =
  let func (m:Map<'k, 'a>) (f:'k -> 'a -> 'b) : Map<'k, 'b> = Map.map f m

为了完整性,您需要在F#Interactive中获取MethodInfo

open System.Reflection

let t = Assembly.GetExecutingAssembly().GetTypes() |> Seq.find (fun t -> t.Name = "Test")
let mi = t.GetMethods() |> Seq.find (fun m -> m.Name = "func")

该方法是通用的,您可以打印通用参数的名称:

for a in mi.GetGenericArguments() do
  printfn " - %s" a.Name

下面的递归函数将格式化一个可能包含泛型参数的类型(它打印泛型参数的名称,它还支持Map和函数,但没有其他内容:

let rec formatType (typ:System.Type) = 
  if typ.IsGenericParameter then typ.Name
  else 
    let args = 
      if typ.IsGenericType then 
        typ.GetGenericArguments() 
        |> Seq.map formatType |> List.ofSeq
      else []
    match typ.Name, args with
    | "FSharpFunc`2", [t1; t2] -> sprintf "(%s -> %s)" t1 t2
    | "FSharpMap`2", [t1; t2] -> sprintf "Map<%s, %s>" t1 t2
    | _ -> failwith "Not supported"

如果你那么获得所有参数&amp;返回类型的方法:

[ for p in mi.GetParameters() do yield p.ParameterType
  yield mi.ReturnType ]
|> List.map formatType
|> String.concat " -> "

您会得到一个结果,这是您在F#中看到的类型签名:

val it:string = "Map<k, a> -> (k -> (a -> b)) -> Map<k, b>"

此签名包含所有通用参数。这是一个最小的示例,它与签名中的名称和泛型参数的名称不匹配,但您可以在此示例后轻松执行此操作(只需检查typ.IsGenericParameter返回true的类型并使用他们的名字将它们与类型中其他地方的相应通用参数相匹配。

答案 1 :(得分:0)

如果您知道f#函数(表示为静态.NET方法的MethodInfo对象)也有方法GetGenericMethodDefinition,那么实际上它很容易。 这似乎与f#中的typedefof< >运算符一样,以这种方式获取MethodInfo对象的泛型定义。
鉴于该函数确实是一个通用函数,可以使用IsGenericMethodDefinition进行检查。

SignaturePrinter下面的任何泛型数据类型

都可以使用任何泛型函数
type Box<'a, 'b> = Box of 'a * 'b

前几位助手

let getFn e =
    let rec name e =
        match e with
            | Call (x, mi, y) -> mi
            | Lambda (_, body) -> name body
            | _ -> failwith <| sprintf "not a function %A" e
    name e

let join sep (ss: string []) = String.Join(sep, ss)

let wrap sepB sepE s = sprintf "%s%s%s" sepB s sepE

let generics (ts:Type[]) = 
    if Array.length ts > 0 then
        ts
        |> Array.map (fun x -> x.Name)
        |> join ","
        |> wrap "<" ">"
    else ""

let genericDef (mi:MethodInfo) = mi.GetGenericMethodDefinition()

然后使用自动报价的签名打印机

type SignaturePrinter = class end
    with
    static member Print ([<ReflectedDefinition>] f : Expr<'a -> 'b>) =
        let mi = getFn f |> genericDef
        let typeSig (t:Type) =
            let sanitizedName (n:string) = n.Split('`') |> Array.head
            [|sanitizedName t.Name; generics t.GenericTypeArguments|]
            |> join ""
        let fnParams (m:MethodInfo) = 
            m.GetParameters()
            |> Array.map (fun x -> typeSig x.ParameterType)
            |> join " -> "

        [|
            [|mi.Name; mi.GetGenericArguments() |> generics; " ::"|] |> join ""
            fnParams mi
            typeSig mi.ReturnType
        |]
        |> join " -> "

要测试的一些通用方法

let iToS<'x, 'y> (b : Box<'x, 'y>) : Box<string, 'y> =
    let (Box (i,v)) = b 
    Box ((sprintf "%A" i), v)
let iToB (Box (i:int, v:'a)) = Box (i > 0, v)
let fixedRet (Box (i:int, v:'z)) = Box (true, false)

测试代码

let s1 = SignaturePrinter.Print iToS 
//iToS<x,y> :: -> Box<x,y> -> Box<String,y>
let s2 = SignaturePrinter.Print fixedRet
//fixedRet<z> :: -> Box<Int32,z> -> Box<Boolean,Boolean>
let s3 = SignaturePrinter.Print iToB
//iToB<a> :: -> Box<Int32,a> -> Box<Boolean,a>