强制转换和转换 (F#)

本文介绍对 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.CharUnicode 字符。
enum 转换为枚举类型。

除了内置基元类型,还可以将这些运算符与实现 op_Explicit 的类型或 op_Implicit 具有适当签名的方法一起使用。 例如,int转换运算符适用于提供将类型作为参数并返回int的静态方法op_Explicit的任意类型。 作为返回类型无法重载方法的一般规则的特殊例外,您可以为此和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# 中,规则略有不同。 将参数传递给对象类型的方法时,将自动应用向上转换。 但是,对于模块中的允许绑定函数,除非参数类型声明为灵活类型,否则升级不是自动的。 有关详细信息,请参阅 灵活类型

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

还可以使用 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 :> Base1let 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# 通过转换运算符使用显式扩大数值类型。 例如,大多数数值类型(例如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])

当源类型和目标类型在类型推理期间已知时,会自动进行int32加宽。int64int32nativeintint32double 因此,在前面的示例等情况下, 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:3391op_Implicit 在非方法参数上,默认为 on)
  • /warnon:3395op_Implicit 在方法参数中)

另请参阅