匿名记录

匿名记录是命名值的简单聚合,在使用前无需声明。 你可以将它们声明为结构或引用类型。 默认情况下,它们是引用类型。

语法

以下示例演示了匿名记录语法。 以 [item] 分隔的项是可选的。

// Construct an anonymous record
let value-name = [struct] {| Label1: Type1; Label2: Type2; ...|}

// Use an anonymous record as a type parameter
let value-name = Type-Name<[struct] {| Label1: Type1; Label2: Type2; ...|}>

// Define a parameter with an anonymous record as input
let function-name (arg-name: [struct] {| Label1: Type1; Label2: Type2; ...|}) ...

基本用法

最好将匿名记录视为无需在实例化之前声明的 F# 记录类型。

例如,下面说明了如何与生成匿名记录的函数进行交互:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    {| Diameter = d; Area = a; Circumference = c |}

let r = 2.0
let stats = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
    r stats.Diameter stats.Area stats.Circumference

以下示例使用将匿名记录作为输入的 printCircleStats 函数补充说明前面的示例:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    {| Diameter = d; Area = a; Circumference = c |}

let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

let r = 2.0
let stats = getCircleStats r
printCircleStats r stats

如果使用与输入类型“形状”不同的任何匿名记录类型调用 printCircleStats,将无法进行编译:

printCircleStats r {| Diameter = 2.0; Area = 4.0; MyCircumference = 12.566371 |}
// Two anonymous record types have mismatched sets of field names
// '["Area"; "Circumference"; "Diameter"]' and '["Area"; "Diameter"; "MyCircumference"]'

结构匿名记录

也可以使用可选的 struct 关键字将匿名记录定义为结构。 以下示例通过生成和使用结构匿名记录补充说明前面的示例:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    // Note that the keyword comes before the '{| |}' brace pair
    struct {| Area = a; Circumference = c; Diameter = d |}

// the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

let r = 2.0
let stats = getCircleStats r
printCircleStats r stats

结构推理

结构匿名记录还允许“结构推理”,从而无需在调用站点指定 struct 关键字。 以下示例在调用 printCircleStats 时省略了 struct 关键字:


let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

printCircleStats r {| Area = 4.0; Circumference = 12.6; Diameter = 12.6 |}

反向模式 - 当输入类型不是结构匿名记录时指定 struct - 将无法编译。

将匿名记录嵌入其他类型

声明用例为记录的可区分联合很有用。 但是,如果记录中的数据与可区分联合的类型相同,则必须将所有类型定义为相互递归。 使用匿名记录可避免此限制。 下面是一个类型示例以及对其进行模式匹配的函数:

type FullName = { FirstName: string; LastName: string }

// Note that using a named record for Manager and Executive would require mutually recursive definitions.
type Employee =
    | Engineer of FullName
    | Manager of {| Name: FullName; Reports: Employee list |}
    | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |}

let getFirstName e =
    match e with
    | Engineer fullName -> fullName.FirstName
    | Manager m -> m.Name.FirstName
    | Executive ex -> ex.Name.FirstName

复制和更新表达式

匿名记录支持使用复制和更新表达式进行构造。 例如,下面说明了如何构造匿名记录的新实例,该实例复制现有实例的数据:

let data = {| X = 1; Y = 2 |}
let data' = {| data with Y = 3 |}

但是,与命名记录不同,匿名记录允许你使用复制和更新表达式构造完全不同的窗体。 以下示例采用与上一个示例相同的匿名记录并将其扩展为新的匿名记录:

let data = {| X = 1; Y = 2 |}
let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |}

也可以通过命名记录的实例构造匿名记录:

type R = { X: int }
let data = { X = 1 }
let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |}

还可以在引用和结构匿名记录之间复制数据:

// Copy data from a reference record into a struct anonymous record
type R1 = { X: int }
let r1 = { X = 1 }

let data1 = struct {| r1 with Y = 1 |}

// Copy data from a struct record into a reference anonymous record
[<Struct>]
type R2 = { X: int }
let r2 = { X = 1 }

let data2 = {| r1 with Y = 1 |}

// Copy the reference anonymous record data into a struct anonymous record
let data3 = struct {| data2 with Z = r2.X |}

匿名记录的属性

匿名记录具有许多特征,这些特征对于充分理解如何使用它们至关重要。

匿名记录是名义类型

匿名记录是名义类型。 最好将其视为无需预先声明的命名记录类型(也是名义类型)。

请看以下包含两个匿名记录声明的示例:

let x = {| X = 1 |}
let y = {| Y = 1 |}

xy 值具有不同的类型并且彼此不兼容。 它们不可相等,也不可比较。 为了说明这一点,我们来看一个命名记录等效项:

type X = { X: int }
type Y = { Y: int }

let x = { X = 1 }
let y = { Y = 1 }

涉及类型相等或比较时,匿名记录与其命名记录等效项相比,没有任何本质上的不同。

匿名记录使用结构相等和比较

与记录类型一样,匿名记录在结构上是可相等和可比较的。 仅当所有构成类型都支持相等和比较时(像记录类型一样),才是如此。 若要支持相等或比较,两条匿名记录必须具有相同的“形状”。

{| a = 1+1 |} = {| a = 2 |} // true
{| a = 1+1 |} > {| a = 1 |} // true

// error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]'
{| a = 1 + 1 |} = {| a = 2;  b = 1|}

匿名记录可序列化

可以像对命名记录一样序列化匿名记录。 下面是使用 Newtonsoft.Json 的示例:

open Newtonsoft.Json

let phillip' = {| name="Phillip"; age=28 |}
let philStr = JsonConvert.SerializeObject(phillip')

let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(philStr)
printfn $"Name: {phillip.name} Age: %d{phillip.age}"

使用匿名记录时,无需为序列化/反序列化类型预先定义域,因此非常适合通过网络发送轻量级数据。

匿名记录与 C# 匿名类型互操作

可以使用需要使用 C# 匿名类型的 .NET API。 通过使用匿名记录,可以与 C# 匿名类型轻松互操作。 以下示例演示如何使用匿名记录调用需要匿名类型的 LINQ 重载:

open System.Linq

let names = [ "Ana"; "Felipe"; "Emilia"]
let nameGrouping = names.Select(fun n -> {| Name = n; FirstLetter = n[0] |})
for ng in nameGrouping do
    printfn $"{ng.Name} has first letter {ng.FirstLetter}"

.NET 中使用的许多其他 API 需要传入匿名类型。 匿名记录是用于处理它们的工具。

限制

匿名记录在使用上有一些限制。 有些是其设计所固有的,有些则是可以改变的。

模式匹配限制

与命名记录不同,匿名记录不支持模式匹配。 原因有三:

  1. 与命名记录类型不同,模式必须考虑匿名记录的每个字段。 这是因为匿名记录不支持结构子类型化 - 它们是名义类型。
  2. 由于 (1),无法在模式匹配表达式中使用附加模式,因为每个不同的模式都意味着不同的匿名记录类型。
  3. 由于 (2),任何匿名记录模式都比使用“点”表示法更冗长。

有一个开放的语言建议,即,允许在有限的上下文中进行模式匹配

可变性限制

目前无法使用 mutable 数据定义匿名记录。 有一个开放的语言建议,即,允许使用可变数据。

结构匿名记录限制

无法将结构匿名记录声明为 IsByRefLikeIsReadOnly。 对于 IsByRefLikeIsReadOnly 匿名记录,有一个开放的语言建议