本文介绍对 F# 中的类型转换的支持。
算术类型
F# 为各种基元类型(例如整数和浮点类型之间的算术转换)提供转换运算符。 整型和字符转换运算符已选中和未选中的窗体;浮点运算符和 enum
转换运算符不。 未选中的表单在定义中 FSharp.Core.Operators
,并且选中的表单在中 FSharp.Core.Operators.Checked
定义。 如果生成的值超出目标类型的限制,则选中的表单会检查溢出并生成运行时异常。
其中每个运算符的名称与目标类型的名称相同。 例如,在以下代码中,其中显式批注了类型, byte
显示两种不同的含义。 第一个匹配项是类型,第二个是转换运算符。
let x : int = 5
let b : byte = byte x
下表显示了在 F# 中定义的转换运算符。
操作员 | DESCRIPTION |
---|---|
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
转换运算符适用于提供将类型作为参数并返回int
的静态方法op_Explicit
的任意类型。 作为返回类型无法重载方法的一般规则的特殊例外,您可以为此和op_Explicit
op_Implicit
执行此作。
枚举类型
该 enum
运算符是一个泛型运算符,它采用一个类型参数,该参数表示要转换为的类型 enum
。 当它转换为枚举类型时,类型推理会尝试确定要转换为的类型 enum
。 在下面的示例中,该变量 col1
未显式批注,但其类型是从以后的相等性测试推断的。 因此,编译器可以推断要转换为 Color
枚举。 或者,可以提供类型批注,如 col2
以下示例所示。
type Color =
| Red = 1
| Green = 2
| Blue = 3
// The target type of the conversion cannot be determined by type inference, so the type parameter must be explicit.
let col1 = enum<Color> 1
// The target type is supplied by a type annotation.
let col2 : Color = enum 2
还可以将目标枚举类型显式指定为类型参数,如以下代码所示:
let col3 = enum<Color> 3
请注意,仅当枚举的基础类型与要转换的类型兼容时,枚举强制转换才起作用。 在以下代码中,由于两者之间不匹配 int32
, uint32
转换无法编译。
// Error: types are incompatible
let col4 : Color = enum 2u
有关详细信息,请参阅 枚举。
强制转换对象类型
对象层次结构中的类型之间的转换是面向对象的编程的基础。 有两种基本类型的转换:向上转换(向上转换)和向下转换(向下转换)。 向上转换层次结构意味着从派生对象引用转换为基对象引用。 只要基类位于派生类的继承层次结构中,此类强制转换就可正常工作。 仅当对象实际上是正确目标(派生)类型的实例或派生自目标类型的类型时,才会从基对象引用向下强制转换层次结构。
F# 为这些类型的转换提供运算符。 运算符 :>
向上强制转换层次结构,运算符 :?>
向下强制转换层次结构。
向上转换
在许多面向对象的语言中,向上转换是隐式的;在 F# 中,规则略有不同。 将参数传递给对象类型的方法时,将自动应用向上转换。 但是,对于模块中的允许绑定函数,除非参数类型声明为灵活类型,否则升级不是自动的。 有关详细信息,请参阅 灵活类型。
:>
运算符执行静态强制转换,这意味着转换的成功在编译时确定。 如果成功使用 :>
编译的强制转换,则它是有效的强制转换,在运行时没有失败的机会。
还可以使用 upcast
运算符执行此类转换。 以下表达式指定层次结构的转换:
upcast expression
使用向上转换运算符时,编译器会尝试推断要从上下文转换为的类型。 如果编译器无法确定目标类型,编译器将报告错误。 可能需要类型批注。
向下转换
:?>
该运算符执行动态强制转换,这意味着转换的成功在运行时确定。 未在编译时检查使用 :?>
运算符的强制转换;但在运行时,将尝试强制转换为指定类型。 如果对象与目标类型兼容,则转换成功。 如果对象与目标类型不兼容,运行时将引发一个 InvalidCastException
。
还可以使用 downcast
运算符执行动态类型转换。 以下表达式指定从程序上下文推断的层次结构中的转换:
downcast expression
至于 upcast
运算符,如果编译器无法从上下文推断特定目标类型,则报告错误。 可能需要类型批注。
以下代码说明了如何使用 :>
和 :?>
运算符。 该代码演示了 :?>
在知道转换成功时最好使用运算符,因为在转换失败时会引发 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
let base1: Base1 = upcast d1
。
类型批注是必需的,因为 upcast
本身无法确定基类。
隐式上播转换
在以下情况下插入隐式向上转换:
向具有已知命名类型的函数或方法提供参数时。 这包括当计算表达式或切片等构造成为方法调用时。
分配或改变具有已知命名类型的记录字段或属性时。
当某个或
match
表达式的if/then/else
分支具有另一个分支或整体已知类型的已知目标类型时。当列表、数组或序列表达式的元素具有已知的目标类型时。
例如,考虑以下代码:
open System
open System.IO
let findInputSource () : TextReader =
if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
// On Monday a TextReader
Console.In
else
// On other days a StreamReader
File.OpenText("path.txt")
此处是条件计算的分支,以及StreamReader
分别计算的TextReader
分支。 在第二个分支上,已知目标类型来自 TextReader
方法上的类型批注以及第一个分支。 这意味着第二个分支不需要任何向上转换。
若要在每次使用其他隐式向上转换时显示警告,可以启用警告 3388(/warnon:3388
或属性 <WarnOn>3388</WarnOn>
)。
隐式数值转换
在大多数情况下,F# 通过转换运算符使用显式扩大数值类型。 例如,大多数数值类型(例如int8
或int16
或从中)或源float32
float64
或目标类型未知时,需要显式扩大。
但是,允许将 32 位整数的隐式扩大为 64 位整数,这与隐式向上转换的情况相同。 例如,请考虑典型的 API 形状:
type Tensor(…) =
static member Create(sizes: seq<int64>) = Tensor(…)
可以使用 int64 的整数文本:
Tensor.Create([100L; 10L; 10L])
或 int32 的整数文本:
Tensor.Create([int64 100; int64 10; int64 10])
当源类型和目标类型在类型推理期间已知时,会自动进行int32
加宽。int64
int32
nativeint
int32
double
因此,在前面的示例等情况下, int32
可以使用文本:
Tensor.Create([100; 10; 10])
还可以选择启用警告 3389(/warnon:3389
或属性 <WarnOn>3389</WarnOn>
),以在每次使用隐式数值加宽时显示警告。
.NET 样式隐式转换
.NET API 允许定义 op_Implicit
静态方法在类型之间提供隐式转换。 将参数传递给方法时,这些参数会自动在 F# 代码中应用。 例如,请考虑对方法进行显式调用 op_Implicit
的以下代码:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")
.当类型可用于源表达式和目标类型时,会自动为参数表达式应用 NET 样式 op_Implicit
转换:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")
还可以选择启用警告 3395(/warnon:3395
或属性 <WarnOn>3395</WarnOn>
)以显示每个点的警告。使用 NET 样式隐式转换。
.NET 样式 op_Implicit
转换也会自动应用于与隐式向上转换相同的非方法自变量表达式。 但是,当广泛使用或不当时,隐式转换可能会与类型推理交互不佳,并导致难以理解的代码。 因此,在非参数位置使用时,这些始终会生成警告。
在每一点显示一个警告。NET 样式隐式转换用于非方法参数,可以启用警告 3391(/warnon:3391
或属性 <WarnOn>3391</WarnOn>
)。
与转换相关的警告摘要
为隐式转换的使用提供了以下可选警告:
-
/warnon:3388
(其他隐式向上转换) -
/warnon:3389
(隐式数值加宽) -
/warnon:3391
(op_Implicit
在非方法参数上,默认为 on) -
/warnon:3395
(op_Implicit
在方法参数中)