使用 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
期望的內容,它將返回正確的方法資訊。
這解決了上面列出的所有問題。