问题 检索F#函数的MethodInfo


我想编写一个函数,它将函数f作为参数,并返回与f关联的System.Reflection.MethodInfo。

我不太确定它是否可行。


8446
2017-10-15 21:26


起源

你打算用MethodInfo做什么? - Brian
我尝试使用erm .. TryGetReflectedDefinition函数获得反映的定义。 - Stringer
我在F#中什么都不知道,但在o'caml你可以使用预处理器(我不知道F#中是否有类似的东西)groups.google.com/group/fa.caml/browse_thread/thread/... - LB40


答案:


所以,我终于找到了解决方案。非常hacky,但嘿!有用! (编辑:仅在调试模式下)。

let Foo (f:S -> A[] -> B[] -> C[] -> D[] -> unit) =
    let ty     = f.GetType()
    let argty  = [|typeof<S>; typeof<A[]>; typeof<B[]>; typeof<C[]>;typeof<D[]>|]
    let mi     = ty.GetMethod("Invoke", argty)
    let il     = mi.GetMethodBody().GetILAsByteArray()
    let offset = 9//mi.GetMethodBody().MaxStackSize

    let token  = System.BitConverter.ToInt32(il, offset)    
    let mb     = ty.Module.ResolveMethod(token)

    match Expr.TryGetReflectedDefinition mb with
    | Some ex -> printfn "success %A" e
    | None ->  failwith "failed"

它工作得很好,即使f在另一个程序集(.dll)中定义,或者在Foo调用发生时也是如此。 它还不完全通用,因为我必须定义argty是什么,但我确信我可以写一个函数来完成它。

在写完这段代码之后,Dustin对同一个问题有一个类似的解决方案,尽管在C#中(见它) 这里)。

编辑: 所以这是一个用法示例:

open System
open Microsoft.FSharp.Quotations

[<ReflectedDefinition>]
let F (sv:int) (a:int[]) (b:int[]) (c:int[]) (d:int[]) =
    let temp = a.[2] + b.[3]
    c.[0] <- temp
    ()

let Foo (f:S -> A[] -> B[] -> C[] -> D[] -> unit) =
    let ty     = f.GetType()
    let arr    = ty.BaseType.GetGenericArguments()
    let argty  = Array.init (arr.Length-1) (fun i -> arr.[i])

    let mi     = ty.GetMethod("Invoke", argty)
    let il     = mi.GetMethodBody().GetILAsByteArray()
    let offset = 9
    let token  = System.BitConverter.ToInt32(il, offset)
    let mb     = ty.Module.ResolveMethod(token)
    mb

let main () =
  let mb = Foo F
  printfn "%s" mb.Name

  match Expr.TryGetReflectedDefinition mb with
  | None -> ()
  | Some(e) -> printfn "%A" e

do main ()

如果函数是反射定义,它的作用是打印F的名称和AST。

经过进一步调查后,碰巧这个hack只能在调试模式下工作(F必须是一个函数值以及一个顶级定义),所以不妨说它是一个 不可能 要做的事

这是调试/发布版本中FSharpFunc的Invoke方法的IL代码:

调试模式:

.method /*06000007*/ public strict virtual 
        instance class [FSharp.Core/*23000002*/]Microsoft.FSharp.Core.Unit/*01000006*/ 
        Invoke(int32 sv,
               int32[] a,
               int32[] b,
               int32[] c,
               int32[] d) cil managed
// SIG: 20 05 12 19 08 1D 08 1D 08 1D 08 1D 08
{
  // Method begins at RVA 0x21e4
  // Code size       16 (0x10)
  .maxstack  9
  IL_0000:  /* 00   |                  */ nop
  IL_0001:  /* 03   |                  */ ldarg.1
  IL_0002:  /* 04   |                  */ ldarg.2
  IL_0003:  /* 05   |                  */ ldarg.3
  IL_0004:  /* 0E   | 04               */ ldarg.s    c
  IL_0006:  /* 0E   | 05               */ ldarg.s    d
  IL_0008:  /* 28   | (06)000001       */ call       void Program/*02000002*/::F(int32,
                                                                                 int32[],
                                                                                 int32[],
                                                                                 int32[],
                                                                                 int32[]) /* 06000001 */
  IL_000d:  /* 00   |                  */ nop
  IL_000e:  /* 14   |                  */ ldnull
  IL_000f:  /* 2A   |                  */ ret
} // end of method mb@25::Invoke

发布模式:

method public strict virtual instance class [FSharp.Core]Microsoft.FSharp.Core.Unit 
        Invoke(int32 sv,
               int32[] a,
               int32[] b,
               int32[] c,
               int32[] d) cil managed
{
  // Code size       28 (0x1c)
  .maxstack  7
  .locals init ([0] int32 V_0)
  IL_0000:  nop
  IL_0001:  ldarg.2
  IL_0002:  ldc.i4.2
  IL_0003:  ldelem     [mscorlib]System.Int32
  IL_0008:  ldarg.3
  IL_0009:  ldc.i4.3
  IL_000a:  ldelem     [mscorlib]System.Int32
  IL_000f:  add
  IL_0010:  stloc.0
  IL_0011:  ldarg.s    c
  IL_0013:  ldc.i4.0
  IL_0014:  ldloc.0
  IL_0015:  stelem     [mscorlib]System.Int32
  IL_001a:  ldnull
  IL_001b:  ret
} // end of method mb@25::Invoke

您可以看到,在发布模式下,编译器将F的代码内联到Invoke方法中,因此调用F的信息(以及检索令牌的可能性)消失了。


5
2017-10-16 07:42



如果这适合你,你会想要接受它作为答案。 - kersny
你能举个例子吗?我理解解决方案的一般概念,但我不明白为什么f具有它所具有的类型。 - Kurt Schelfthout


以下程序有帮助吗?

module Program

[<ReflectedDefinition>]
let F x =
    x + 1

let Main() =
    let x = F 4    
    let a = System.Reflection.Assembly.GetExecutingAssembly()
    let modu = a.GetType("Program")
    let methodInfo = modu.GetMethod("F")
    let reflDefnOpt = Microsoft.FSharp.Quotations.Expr.TryGetReflectedDefinition(methodInfo)
    match reflDefnOpt with
    | None -> printfn "failed"
    | Some(e) -> printfn "success %A" e

Main()    

3
2017-10-15 22:12



是的,非常喜欢,我希望我不知道方法名称(“F”)或模块。 - Stringer


这不是(容易)可能的。要注意的是,当你写:

let printFunctionName f =
    let mi = getMethodInfo f
    printfn "%s" mi.Name

参数'f'只是FSharpFunc <类型的一个实例>。所以以下都是可能的:

printFunctionName (fun x -> x + 1)    // Lambda expression
printFunctionName String.ToUpper      // Function value
printFunctionName (List.map id)       // Curried function
printFunctionNAme (not >> List.empty) // Function composition

无论哪种情况,都没有直截了当的答案


2
2017-10-15 22:21



也许这有帮助,我知道f总是一个Function值。你有什么建议的?我会采取任何黑客.. - Stringer


我不知道是否有任何功能的一般答案,但如果你的功能很简单('a - >'b)那么你可以写

let getMethodInfo (f : 'a -> 'b) = (FastFunc.ToConverter f).Method


1



谢谢,我试过了,但似乎没有用.. - Stringer