记录表示命名值的简单聚合,可以选择使用成员。 它们可以是结构或引用类型。 默认情况下,它们是引用类型。
语法
[ attributes ]
type [accessibility-modifier] typename =
    [accessibility-modifier] { 
        [ mutable ] label1 : type1;
        [ mutable ] label2 : type2;
        ... 
    }
    [ member-list ]
前accessibility modifiertypename者会影响整个类型的可见性,默认情况下是public。 第二 accessibility modifier 个仅影响构造函数和字段。
注解
在前面的语法中,typename 是记录类型的名称,label1 和 label2 是值的名称,称为标签,type1 和 type2 是这些值的类型。 
              member-list 是类型成员的可选列表。  可以使用特性 [<Struct>] 创建结构记录,而不是引用类型的记录。
下面是一些示例。
// Labels are separated by semicolons when defined on the same line.
type Point = { X: float; Y: float; Z: float }
// You can define labels on their own line with or without a semicolon.
type Customer =
    { First: string
      Last: string
      SSN: uint32
      AccountNumber: uint32 }
// A struct record.
[<Struct>]
type StructPoint = { X: float; Y: float; Z: float }
当每个标签位于单独的行上时,分号是可选的。
可以在称为 记录表达式的表达式中设置值。 编译器从使用的标签推断类型(如果标签与其他记录类型的标签完全不同)。 大括号 ({ }) 将记录表达式括起来。 下面的代码显示了一个记录表达式,该表达式使用三个带有标签 x的浮点元素初始化记录, y 以及 z。
let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }
如果可能有另一个类型也具有相同标签,请不要使用缩短的窗体。
type Point = { X: float; Y: float; Z: float }
type Point3D = { X: float; Y: float; Z: float }
// Ambiguity: Point or Point3D?
let mypoint3D = { X = 1.0; Y = 1.0; Z = 0.0 }
最近声明类型的标签优先于以前声明的类型,因此在前面的示例中 mypoint3D ,推断为 Point3D。 可以显式指定记录类型,如以下代码所示。
let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }
可以为记录类型定义方法,就像类类型一样。
使用记录表达式创建记录
可以使用记录中定义的标签来初始化记录。 执行此作的表达式称为 记录表达式。 使用大括号将记录表达式括起来,并使用分号作为分隔符。
以下示例演示如何创建记录。
type MyRecord = { X: int; Y: int; Z: int }
let myRecord1 = { X = 1; Y = 2; Z = 3 }
记录表达式和类型定义中最后一个字段后面的分号是可选的,无论字段是否都位于一行中。
创建记录时,必须为每个字段提供值。 不能引用任何字段的初始化表达式中其他字段的值。
在以下代码中,从字段的名称推断出其类型 myRecord2 。 (可选)可以显式指定类型名称。
let myRecord2 =
    { MyRecord.X = 1
      MyRecord.Y = 2
      MyRecord.Z = 3 }
当必须复制现有记录时,另一种记录构造形式可能很有用,并且可能会更改某些字段值。 以下代码行说明了这一点。
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
此形式的记录表达式称为 复制和更新记录表达式。
记录默认不可变;但是,可以使用复制和更新表达式轻松创建修改的记录。 还可以显式指定可变字段。
type Car =
    { Make: string
      Model: string
      mutable Odometer: int }
let myCar =
    { Make = "Fabrikam"
      Model = "Coupe"
      Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
不要将 DefaultValue 属性用于记录字段。 更好的方法是使用初始化为默认值的字段定义记录的默认实例,然后使用复制和更新记录表达式来设置与默认值不同的任何字段。
// Rather than use [<DefaultValue>], define a default record.
type MyRecord =
    { Field1 : int
      Field2 : int }
let defaultRecord1 = { Field1 = 0; Field2 = 0 }
let defaultRecord2 = { Field1 = 1; Field2 = 25 }
// Use the with keyword to populate only a few chosen fields
// and leave the rest with default values.
let rr3 = { defaultRecord1 with Field2 = 42 }
创建相互递归记录
有时在创建记录时,可能需要让记录依赖于之后要定义的另一种类型。 除非将记录类型定义为相互递归,否则这是一个编译错误。
使用关键字定义 and 相互递归记录。 这样,便可以将 2 个或多个记录类型链接在一起。
例如,以下代码将 a Person 和 Address 类型定义为相互递归:
// Create a Person type and use the Address type that is not defined
type Person =
    { Name: string
      Age: int
      Address: Address }
// Define the Address type which is used in the Person record
and Address =
    { Line1: string
      Line2: string
      PostCode: string
      Occupant: Person }
若要创建这两个实例,请执行以下作:
// Create a Person type and use the Address type that is not defined
let rec person =
    {
        Name = "Person name"
        Age = 12
        Address =
            {
                Line1 = "line 1"
                Line2 = "line 2"
                PostCode = "abc123"
                Occupant = person
            }
    }
如果要在没有关键字的情况下 and 定义上一个示例,则它不会编译。 
              and对于相互递归定义,关键字是必需的。
与记录的模式匹配
记录可用于模式匹配。 可以显式指定某些字段,并为发生匹配时分配的其他字段提供变量。 下面的代码示例对此进行了说明。
type Point3D = { X: float; Y: float; Z: float }
let evaluatePoint (point: Point3D) =
    match point with
    | { X = 0.0; Y = 0.0; Z = 0.0 } -> printfn "Point is at the origin."
    | { X = xVal; Y = 0.0; Z = 0.0 } -> printfn "Point is on the x-axis. Value is %f." xVal
    | { X = 0.0; Y = yVal; Z = 0.0 } -> printfn "Point is on the y-axis. Value is %f." yVal
    | { X = 0.0; Y = 0.0; Z = zVal } -> printfn "Point is on the z-axis. Value is %f." zVal
    | { X = xVal; Y = yVal; Z = zVal } -> printfn "Point is at (%f, %f, %f)." xVal yVal zVal
evaluatePoint { X = 0.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 100.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 10.0; Y = 0.0; Z = -1.0 }
此代码的输出如下所示。
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
记录和成员
可以在记录上指定成员,就像使用类一样。 不支持字段。 一种常见方法是定义 Default 静态成员,以便轻松进行记录构造:
type Person =
    { Name: string
      Age: int
      Address: string }
    static member Default =
        { Name = "Phillip"
          Age = 12
          Address = "123 happy fun street" }
let defaultPerson = Person.Default
如果使用自标识符,该标识符引用其成员被调用的记录的实例:
type Person =
    { Name: string
      Age: int
      Address: string }
    member this.WeirdToString() =
        this.Name + this.Address + string this.Age
let p = { Name = "a"; Age = 12; Address = "abc123" }
let weirdString = p.WeirdToString()
记录上的辅助功能修饰符
以下代码演示了辅助功能修饰符的使用。 项目中有三个文件: Module1.fs和 Test1.fsTest2.fs。 模块 1 中定义了内部记录类型和具有专用构造函数的记录类型。
// Module1.fs
module Module1
type internal internalRecd = { X: int }
type recdWithInternalCtor = private { Y: int }
              Test1.fs在文件中,必须使用访问修饰符初始化internal内部记录,这是因为值和记录的保护级别必须匹配,并且两者都必须属于同一程序集。
// Test1.fs
module Test1
open Module1
let myInternalRecd1 = { X = 2 } // This line will cause a compiler error.
let internal myInternalRecd2 = { X = 4 } // This is OK
在 Test2.fs 文件中,由于构造函数的保护级别,无法直接初始化具有专用构造函数的记录。
// Test2.fs
module Test2
open Module1
let myRecdWithInternalCtor = { Y = 6 } // This line will cause a compiler error.
有关辅助功能修饰符的详细信息,请参阅 访问控制 文章。
记录和类之间的差异
记录字段不同于类字段,因为它们自动公开为属性,用于创建和复制记录。 记录构造也不同于类构造。 在记录类型中,不能定义构造函数。 相反,本主题中所述的构造语法适用。 类在构造函数参数、字段和属性之间没有直接关系。
与联合和结构类型一样,记录具有结构相等语义。 类具有引用相等语义。 下面的代码示例演示了这一点。
type RecordTest = { X: int; Y: int }
let record1 = { X = 1; Y = 2 }
let record2 = { X = 1; Y = 2 }
if (record1 = record2) then
    printfn "The records are equal."
else
    printfn "The records are unequal."
此代码的输出如下所示:
The records are equal.
如果使用类编写相同的代码,则两个类对象将不相等,因为两个值表示堆上的两个对象,并且只会比较地址(除非类类型重写 System.Object.Equals 该方法)。
如果需要记录的引用相等性,请在记录上方添加属性 [<ReferenceEquality>] 。