第七章 F# 库(二)
反射(Microsoft.FSharp.Reflection)模块
这个模块包含了 F# 自己的反射(reflection)版本。F#中的某些类型和通用语言运行时(CLR)的类型系统百分之百兼容,但是,它们并不能被 .NET反映准确理解。例如,F# 使用了某种技术实现了联合类型,这对纯 F#代码来说是的;当你使用 BCL 对它进行反射时,看起来有点奇怪,F#的反射系统解决了这种问题。但是,它和 BCL 的 System.Reflection命名空间的混合,因此,如果你反映 F# 类型使用 BCL的类型,就会得到来自 System.Reflection 命名空间的适当类型。
在 F# 中,既可以反射,也可以反射值,差别有一点微妙,因此,最好的解释是用示例。熟悉 .NET反射的人可以把反射类型是使用 Type、EventInfo、FieldInfo、MethodInfo和 PropertyInfo 类型,而把反射值看做是调用它们的成员,比如,用 GetProperty或 InvokeMember动态获得值;然而,反射值提供了高级、易用的系统。
反射类型(Reflection over types):检查组成特定值或类型的类型;
反射值(Reflection over values):检查组成特定组合值(composite value)的值。
反射类型(Reflection over types)
下面示例中函数能够打印任何元组的类型:
open Microsoft.FSharp.Reflection
let printTupleTypes (x: obj) =
let t = x.GetType()
if FSharpType.IsTuple t then
types = FSharpType.GetTupleElements t
printf "("
types
|> Seq.iteri
(fun i t ->
if i <> Seq.length types - 1 then
printf " %s * " t.Name
else
printf "%s" t.Name)
printfn " )"
else
printfn "not a tuple"
printTupleTypes ("hello world", 1)
首选,使用对象的 GetType 方法得到表示这个对象的类型(System.Type);然后,对这个值使用函数 FSharpType.IsTuple检查它是否是元组;接着,再使用函数 FSharpType.GetTupleElements得到一个类型(System.Type)数组,描述组成元组的元素。这些可以表示成 F#的类型,因此,能够递归地调用函数,检查它们的内容。这里,我们知道它们是 .NET BCL的类型,就简单地输出类型的名字,这样,示例运行的结果如下:
( String * Int32 )
反射值(Reflection over values)
假设不想显示元组的类型,而是想显示组成元组的值,需要这样做:使用反射值,还需要使用函数 FSharpValue.GetTupleFields得到一个对象数组,这是组成这个元组的值。这些对象可能是元组,也可能是其他 F# 类型,因此,需要递归调用这个函数,去输出对象的值。然而,在这里,我们知道底层的值来自 BCL 的库,因此,只要简单地使用F# 的printfn函数来输出就行了,F# 的 printf模块在本章的后面有讲解。下面的示例实现了这样一个函数:
open Microsoft.FSharp.Reflection
let printTupleValues (x: obj) =
if FSharpType.IsTuple(x.GetType()) then
let vals = FSharpValue.GetTupleFields x
printf "("
vals
|> Seq.iteri
(fun i v ->
if i <> Seq.length vals - 1 then
printf " %A, " v
else
printf " %A" v)
printfn " )"
else
printfn "not a tuple"
printTupleValues ("hello world", 1)
代码的运行结果如下:
( "hello world", 1 )
反射既可以用于 fsi,它是 F#工具套件中的交互式命令行工具,也可以 F# 库函数 printf族。如果要学习更多有关反射的使用,可以看一看 printf 的源代码,在发布的 \source\fsharp\printf.fs和 \source\fsharp\layout.ml 中。