强制转换和转换 (F#)

本文介绍 F# 对类型转换的支持。

算术类型

F# 为各种基元类型之间的算术转换提供了转换运算符,例如整数和浮点类型之间的转换。 整型和字符转换运算符有已勾选和未勾选两种形式;浮点运算符和 enum 转换运算符没有。 未勾选形式在 FSharp.Core.Operators 中定义,已勾选形式在 FSharp.Core.Operators.Checked 中定义。 如果生成的值超出目标类型的限制,未勾选形式会检查溢出并生成运行时异常。

这些运算符中的每一个都具有与目标类型相同的名称。 例如,在以下代码中,类型已显式注释,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_Explicitop_Implicit 方法的类型。 例如,int 转换运算符适用于提供静态方法 op_Explicit 的任何类型,该方法将类型作为参数并返回 int。 作为不能通过返回类型重载方法的一般规则的特殊例外,你可以对 op_Explicitop_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

请注意,仅当枚举的基础类型与要转换的类型兼容时,枚举强制转换才有效。 在以下代码中,由于 int32uint32 不匹配,转换无法编译。

// Error: types are incompatible
let col4 : Color = enum 2u

有关详细信息,请参阅枚举

强制转换对象类型

对象层次结构中类型之间的转换是面向对象编程的基础。 转换有两种基本类型:向上强制转换(向上转换)和向下强制转换(向下转换)。 向上强制转换层次结构意味着从派生对象引用强制转换为基础对象引用。 只要基类在派生类的继承层次结构中,此类强制转换就可以正常工作。 仅当对象实际上是正确目标(派生)类型的实例或从目标类型派生的类型时,向下强制转换层次结构(从基础对象引用到派生对象引用)才会成功。

F# 为这些类型的转换提供了运算符。 :> 运算符向上强制转换层次结构,:?> 运算符向下强制转换层次结构。

向上转换

在许多面向对象的语言中,向上转换是一种隐式操作;在 F# 中,规则略有不同。 当你将参数传递给对象类型上的方法时,系统会自动应用向上转换。 但是,对于模块中的 let-bound 函数,不会自动进行向上转换,除非参数类型声明为可变类型。 有关详细信息,请参阅可变类型

:> 运算符执行静态强制转换,这意味着在编译时确定强制转换是否成功。 如果使用 :> 的强制转换编译成功,则表示它是有效的强制转换并且在运行时没有失败的可能。

你还可以使用 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

由于泛型运算符 downcastupcast 依赖于类型推理来确定参数和返回类型,因此,可以将前面代码示例中的 let base1 = d1 :> Base1 替换为 let base1: Base1 = upcast d1

需要类型注释,因为 upcast 本身无法确定基类。

隐式向上转换

在以下情况下插入隐式向上转换:

  • 向具有已知命名类型的函数或方法提供参数时。 这包括当诸如计算表达式或切片之类的构造变成方法调用时。

  • 分配或转变具有已知命名类型的记录字段或属性时。

  • if/then/elsematch 表达式的分支具有从另一个分支或整体已知类型产生的已知目标类型时。

  • 当列表、数组或序列表达式的元素具有已知的目标类型时。

例如,考虑以下代码:

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")

这里,条件分支分别计算 TextReaderStreamReader。 在第二个分支上,已知的目标类型是来自方法上的类型注释的 TextReader,它来自第一个分支。 这意味着第二个分支不需要向上转换。

若要在使用附加隐式向上转换的每个点显示警告,可以启用警告 3388(/warnon:3388 或属性 <WarnOn>3388</WarnOn>)。

隐式数值转换

在大多数情况下,F# 通过转换运算符使用数值类型的显式扩大。 例如,大多数数值类型(例如 int8int16,或从 float32float64,或者源或目标类型未知)都需要显式扩大。

但是,在与隐式向上转换相同的情况下,隐式扩大允许将 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])

当类型推理过程中已知晓源类型和目标类型时,int32int64int32nativeint 以及 int32double 的扩大会自动发生。 因此,如前面的示例所示,可以使用 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,默认启用)
  • /warnon:3395(对方法参数使用 op_Implicit

另请参阅