记录 (F#)

记录表示命名值的简单聚合,并可以选择包含成员。

[ attributes ]
type [accessibility-modifier] typename = { 
    [ mutable ] label1 : type1;
    [ mutable ] label2 : type2;
    ...
    }
    member-list

备注

在上述语法中,typename 是记录类型的名称,label1 和 label2 是值的名称(称为“标签”),type1 和 type2 是这些值的类型。 member-list 是可选的类型成员列表。

下面是一些示例。

type Point = { x : float; y: float; z: float; }
type Customer = { First : string; Last: string; SSN: uint32; AccountNumber : uint32; }

如果每个标签单独占一行,则分号是可选的。

可以在称为“记录表达式”的表达式中设置值。 编译器依据所使用的标签推断出类型(如果标签与其他记录类型截然不同)。 大括号 ({ }) 用于括住记录表达式。 下面的代码演示了一个记录表达式,该表达式使用标签 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

不要使用与记录字段的默认值属性。 更好的方法是定义为默认值的初始化的字段的记录的默认实例,然后使用复制和更新记录设置不同的默认值的任何字段的表达式。

// 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 }

与记录一起使用的模式匹配

记录可以与模式匹配一起使用。 可显式指定一些字段并为发生匹配时将要赋值的其他字段提供变量。 下面的代码示例阐释了这一点。

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).

记录与类之间的区别

记录字段与类不同,它们会作为属性自动公开,并用于创建和复制记 录。 记录构造也不同于类构造。 在记录类型中,您不能定义构造函数, 而是要应用本主题中描述的构造语法。 类与构造函数参数、字段和属性没有直接关系。

与联合类型和结构类型类似,记录具有结构相等性语义。 类具有引用相等性语义。 下面的代码示例对此进行了演示。

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."

如果使用类来编写相同的代码,则两个类对象将不相等,因为这两个值表示堆上的两个对象,并且仅对地址进行比较(除非类类型重写 System.Object.Equals 方法)。

请参见

参考

类 (F#)

模式匹配 (F#)

其他资源

F# 类型

F# 语言参考