記錄代表具名值的簡單匯總,選擇性地與成員。 它們可以是結構或參考型別。 它們預設為參考型別。
語法
[ attributes ]
type [accessibility-modifier] typename =
[accessibility-modifier] {
[ mutable ] label1 : type1;
[ mutable ] label2 : type2;
...
}
[ member-list ]
之前typename的 會影響accessibility modifier整個類型的可見性,而且預設為。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和 yz之 float 元素的記錄。
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 個或多個記錄類型連結在一起。
例如,下列程式代碼會將 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.fs和 Test2.fs。 內部記錄類型及具有專用建構函式的記錄類型定義在 Module1 中。
// 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>] 。