Null 值

本主题介绍如何在 F# 中使用 null 值。

F# 9 之前的 Null 值

null 值通常不在 F# 中用于值或变量。 但是,在某些情况下,null 显示为异常值。 如果在 F# 中定义了类型,则不允许将 null 作为常规值,除非对该类型应用 AllowNullLiteral 属性。 如果类型是使用其他一些 .NET 语言定义的,则 null 是一个可能的值,并且当你与此类类型进行互操作时,F# 代码可能会遇到 null 值。

对于在 F# 中定义并严格从 F# 中使用的类型,直接使用 F# 库创建空值的唯一方法是使用 Unchecked.defaultofArray.zeroCreate。 但是,对于从其他 .NET 语言使用的 F# 类型,或者如果将该类型与 F# 中未写入的 API(如 .NET Framework)一起使用,则可能会出现 null 值。

在可能使用另一 .NET 语言中可能为 null 值的引用变量时,可以在 F# 中使用 option 类型。 如果没有对象,则可以使用选项值 option,而不是使用 F# None 类型的 null。 如果有对象,则可将选项值 Some(obj) 用于对象 obj。 有关详细信息,请参阅 选项。 请注意,如果对于 Some xx 恰好是 null,则仍然可以将 null 值打包到选项中。 因此,在值为 None 时使用 null 非常重要。

null 关键字是 F# 中的有效关键字,在使用 .NET Framework API 或其他以另一种 .NET 语言编写的 API 时,必须使用它。 在两种情况下可能需要使用空值:一种是调用 .NET API 并将 null 值作为参数传递时;另一种是解释 .NET 方法调用的返回值或输出参数时。

若要将 null 值传递给 .NET 方法,只需在调用代码中使用 null 关键字。 下面的代码示例对此进行了说明。

open System

// Pass a null value to a .NET method.
let ParseDateTime (str: string) =
    let (success, res) =
        DateTime.TryParse(str, null, System.Globalization.DateTimeStyles.AssumeUniversal)

    if success then Some(res) else None

若要解释从 .NET 方法获取的 null 值,请使用模式匹配(如果可以)。 下面的代码示例演示如何使用模式匹配来解释在尝试读取输入流末尾时从 ReadLine 返回的 null 值。

// Open a file and create a stream reader.
let fileStream1 =
    try
        System.IO.File.OpenRead("TextFile1.txt")
    with :? System.IO.FileNotFoundException ->
        printfn "Error: TextFile1.txt not found."
        exit (1)

let streamReader = new System.IO.StreamReader(fileStream1)

// ProcessNextLine returns false when there is no more input;
// it returns true when there is more input.
let ProcessNextLine nextLine =
    match nextLine with
    | null -> false
    | inputString ->
        match ParseDateTime inputString with
        | Some(date) -> printfn "%s" (date.ToLocalTime().ToString())
        | None -> printfn "Failed to parse the input."

        true

// A null value returned from .NET method ReadLine when there is
// no more input.
while ProcessNextLine(streamReader.ReadLine()) do
    ()

F# 类型的空值也可以以其他方式生成,例如使用 Array.zeroCreate时,它会调用 Unchecked.defaultof。 必须谨慎处理此类代码,使 null 值保持为封装状态。 在仅用于 F# 的库中,无需检查每个函数中的 null 值。 如果要编写库以与其他 .NET 语言进行互操作,可能需要添加对 null 输入参数的检查并引发 ArgumentNullException,就像在 C# 或 Visual Basic 代码中所做的那样。

可以使用以下代码检查任意值是否为 null。

match box value with
| null -> printf "The value is null."
| _ -> printf "The value is not null."

从 F# 9 开始的 Null 值

在 F# 9 中,将额外功能添加到语言中,以处理可将 null 作为值的引用类型。 默认情况下,这些属性处于关闭状态 - 若要打开它们,必须将以下属性放在项目文件中:

<Nullable>enable</Nullable>

这会将 --checknulls+标志 传递给 F# 编译器,并为生成设置 NULLABLE预处理器指令

若要显式启用可为 Null 性,必须为类型声明加上新语法后缀:

type | null

在该语法中,竖线符号 | 表示逻辑 OR,用于构建两个不相交类型集合的并集:基础类型和可为 Null 的引用类型。 这与用于声明 F# 可区分联合的多个用例的语法符号相同:type AB = A | B 表示要么是 A,要么是 B

可为 null 的批注 | null 可以在通常使用引用类型的所有地方使用:

  • 联合类型、记录类型和自定义类型的字段。
  • 现有类型的类型别名。
  • 泛型类型的类型应用。
  • 显式类型批注以允许绑定、参数或返回类型。
  • 对对象编程构造(如成员、属性或字段)键入批注。
type AB = A | B
type AbNull = AB | null

type RecordField = { X: string | null }
type TupleField = string * string | null

type NestedGenerics = { Z : List<List<string | null> | null> | null }

条形符号 | 在 F# 中具有其他用法,这可能会导致语法歧义。 此类情况下,需要用括号将 null 批注类型括起来:

// Unexpected symbol '|' (directly before 'null') in member definition
type DUField = N of string | null

将同一类型的元素用一对 ( ) 括号包装起来以修复此问题。

type DUField = N of (string | null)

在模式匹配中使用时,| 用于分隔不同的模式匹配子句。

match x with
| ?: string | null -> ...

此代码片段实际上等效于代码首先针对 string 类型执行类型测试,然后具有用于处理 null 的单独子句:

match x with
| ?: string 
| null -> ...

重要

出于互操作性目的,向语言添加了额外的 null 相关功能。 在 F# 类型建模中,使用 | null 表示缺失信息并不符合惯例做法,为此目的,请使用 Option 类型(如上所述)。 在样式指南中详细了解与 null 相关的约定

另请参阅