匿名记录

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

语法

以下示例演示匿名记录语法。 标记为[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 匿名记录,有一个开放的语言建议