强制转换和转换 (F#)
本主题介绍 F# 中对于类型转换的支持。
算术类型
F# 提供在各种基元类型之间(例如,整数和浮点类型之间)进行算术转换的转换运算符。 整数和 char 转换运算符提供执行检查和不执行检查两种形式;浮点运算符和 enum 转换运算符则没有。 不执行检查形式在 Microsoft.FSharp.Core.Operators 中定义,而执行检查形式在 Microsoft.FSharp.Core.Operators.Checked 中定义。 执行检查形式将会检查溢出情况,如果结果值超出目标类型的限制,则会生成运行时异常。
上述的每个运算符都与目标类型同名。 例如,在下面的代码中(其中的类型已显式批注),byte 以两种不同的含义出现。 第一个 byte 是类型,而第二个 byte 是转换运算符。
let x : int = 5
let b : byte = byte x
下表显示了 F# 中定义的转换运算符。
运算符 |
说明 |
---|---|
byte |
转换为字节,即,一个 8 位无符号类型。 |
sbyte |
转换为带符号字节。 |
int16 |
转换为 16 位带符号整数。 |
uint16 |
转换为 16 位无符号整数。 |
int32, int |
转换为 32 位带符号整数。 |
uint32 |
转换为 32 位无符号整数。 |
int64 |
转换为 64 位带符号整数。 |
uint64 |
转换为 64 位无符号整数。 |
nativeint |
转换为本机整数。 |
unativeint |
转换为无符号本机整数。 |
float, double |
转换为 64 位双精度 IEEE 浮点数。 |
float32, single |
转换为 32 位单精度 IEEE 浮点数。 |
decimal |
转换为 System.Decimal。 |
char |
转换为 System.Char,即,一个 Unicode 字符。 |
enum |
转换为枚举类型。 |
除了内置基元类型,还可以将这些运算符用于可实现带有适当签名的 op_Explicit 或 op_Implicit 方法的类型。 例如,int 转换运算符可用于任何提供静态方法 op_Explicit 的类型,但前提是该方法接受该类型作为参数并返回 int。 作为一般规则(不能通过返回类型重载方法)的特例,您可以为 op_Explicit 和 op_Implicit 执行此操作。
枚举类型
enum 运算符是一个泛型运算符,它采用一个表示要转换为的 enum 的类型的类型参数。 在转换为枚举类型时,类型推理会尝试确定您想要转换为的 enum 的类型。 在下面的示例中,变量 col1 未显式批注,但其类型可从稍后的相等测试中推理出来。 因此,编译器可以推导出您要转换为 Color 枚举。 或者,您可以提供一个类型批注,就像下面的示例中的 col2 一样。
type Color =
| Red = 1
| Green = 2
| Blue = 3
// The target type of the conversion is determined by type inference.
let col1 = enum 1
// The target type is supplied by a type annotation.
let col2 : Color = enum 2
do
if (col1 = Color.Red) then
printfn "Red"
可以显式指定目标枚举类型作为类型参数,如以下代码所示:
let col3 = enum<Color> 3
请注意枚举转换才会运行,枚举的基础类型将被转换类型兼容。 由于 int32 和 uint32之间,不匹配在下面的代码,将无法编译。
// Error: types are incompatible
let col4 : Color = enum 2u
有关更多信息,请参见 枚举 (F#)。
强制转换对象类型
对象层次结构中各类型之间的转换是面向对象编程的基础。 有两种基本类型的转换:向上转换和向下转换。 按层次结构向上转换意味着,从派生对象引用强制转换为基对象引用。 只要基类位于派生类的继承层次结构中,就可以保证此类强制转换成功。 按层次结构向下转换是从基对象引用转换为派生对象引用。只有在对象实际上是正确的目标(派生的)类型或从目标类型派生的类型的一个实例时,此类转换才会成功。
F# 提供了用于这些转换类型的运算符。 :> 运算符用于按层次结构向上转换,而 :?> 运算符用于按层次结构向下转换。
向上转换
在许多面向对象的语言中,向上转换是隐式的;在 F# 中,规则略有不同。 当您向某个对象类型上的方法传递参数时,将自动应用向上转换。 但是,对于模块中的 let 绑定函数,不会自动执行向上转换,除非将参数类型声明为可变类型。 有关更多信息,请参见可变类型 (F#)。
:> 运算符执行静态强制转换,这意味着强制转换是否成功将在编译时确定。 如果使用 :> 的强制转换编译成功,则表明它是有效的强制转换,不可能会在运行时失败。
也可以使用 upcast 运算符来执行此类转换。 下面的表达式指定按层次结构向上转换。
upcast expression
在使用向上转换运算符时,编译器会尝试根据上下文来推断出要转换为的类型。 如果编译器无法确定目标类型,则编译器将报告错误。
向下转换
:?> 运算符执行动态强制转换,这意味着强制转换是否成功将在运行时确定。 在编译时,不会对使用 :?> 运算符的强制转换进行检查;但在运行时,将会尝试转换为指定的类型。 如果对象与目标类型兼容,则强制转换成功。 如果对象与目标类型不兼容,则运行时将引发 InvalidCastException。
也可以使用 downcast 运算符来执行动态类型转换。 下面的表达式指定按层次结构向下转换为一个根据程序上下文推断出的类型。
downcast expression
对于向上转换运算符,如果编译器无法根据上下文推断出特定的目标类型,则将报告错误。
下面的代码演示如何使用 :> 和 :?> 运算符。 从代码中可以看出,在您知道转换会成功的情况下,最好使用 :?> 运算符,这是因为在转换失败时,它会引发 InvalidCastException。 如果您不确定转换会成功,则使用 match 表达式的类型测试会是更好的选择,这是因为它可以避免生成异常的开销。
type Base1() =
abstract member F : unit -> unit
default u.F() =
printfn "F Base1"
type Derived1() =
inherit Base1()
override u.F() =
printfn "F Derived1"
let d1 : Derived1 = Derived1()
// Upcast to Base1.
let base1 = d1 :> Base1
// This might throw an exception, unless
// you are sure that base1 is really a Derived1 object, as
// is the case here.
let derived1 = base1 :?> Derived1
// If you cannot be sure that b1 is a Derived1 object,
// use a type test, as follows:
let downcastBase1 (b1 : Base1) =
match b1 with
| :? Derived1 as derived1 -> derived1.F()
| _ -> ()
downcastBase1 base1
因为泛型运算符 downcast 和 upcast 依赖于类型推理来确定参数和返回类型,所以在上面的代码中,可以将
let base1 = d1 :> Base1
with
base1 = upcast d1
在前面的代码中,参数类型和返回类型分别是 Derived1 和 Base1。
有关类型测试的更多信息,请参见match 表达式 (F#)。