記錄 (F#)
記錄表示具名值的簡單彙總,並選擇性地搭配成員。 它們可以是結構或參考類型。 根據預設其為參考類型。
語法
[ attributes ]
type [accessibility-modifier] typename =
{ [ mutable ] label1 : type1;
[ mutable ] label2 : type2;
... }
[ member-list ]
備註
在上面的語法中,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 個或多個的記錄類型連結在一起。
例如,下列程式碼將 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()
記錄與類別之間的差異
記錄欄位與類別欄位不同之處是,記錄欄位會自動公開為屬性,而且會用於建立及複製記錄。 記錄建構也與類別建構不同。 在記錄類型中,您無法定義建構函式。 然而,本主題中所述的建構語法卻適用。 類別在建構函式參數、欄位和屬性之間沒有直接關聯性。
如同聯合和結構類型,記錄也具有結構相等的語意。 類別具有參考相等的語意。 下列程式碼範例示範此工作。
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>]
。