使用 F 引号的强大反射
反射很有用但很脆弱。考虑一下:
let mi = typeof<System.String>.GetMethod "StartsWith"
这种代码的问题是:
- 代码不起作用,因为
String.StartsWith
有几个重载 - 即使现在没有任何重载,库的更高版本也可能会添加导致运行时崩溃的重载
- 像
Rename methods
这样的重构工具被反射破坏了。
这意味着我们会遇到编译时已知的运行时崩溃。这似乎不是最理想的。
使用 F#
引用可以避免上述所有问题。我们定义了一些辅助函数:
open FSharp.Quotations
open System.Reflection
let getConstructorInfo (e : Expr<'T>) : ConstructorInfo =
match e with
| Patterns.NewObject (ci, _) -> ci
| _ -> failwithf "Expression has the wrong shape, expected NewObject (_, _) instead got: %A" e
let getMethodInfo (e : Expr<'T>) : MethodInfo =
match e with
| Patterns.Call (_, mi, _) -> mi
| _ -> failwithf "Expression has the wrong shape, expected Call (_, _, _) instead got: %A" e
我们使用这样的函数:
printfn "%A" <| getMethodInfo <@ "".StartsWith "" @>
printfn "%A" <| getMethodInfo <@ List.singleton 1 @>
printfn "%A" <| getConstructorInfo <@ System.String [||] @>
这打印:
Boolean StartsWith(System.String)
Void .ctor(Char[])
Microsoft.FSharp.Collections.FSharpList`1[System.Int32] Singleton[Int32](Int32)
<@ ... @>
意味着不是在 F#
中执行表达式而是生成表示表达式的表达式树。<@ "".StartsWith "" @>
生成一个如下所示的表达式树:Call (Some (Value ("")), StartsWith, [Value ("")])
。此表达式树匹配 getMethodInfo
期望的内容,它将返回正确的方法信息。
这解决了上面列出的所有问题。