資料錄 (F#)
記錄表示具名值的簡單彙總,並選擇性搭配成員。
[ attributes ]
type [accessibility-modifier] typename = {
[ mutable ] label1 : type1;
[ mutable ] label2 : type2;
...
}
member-list
備註
在前一個語法中,typename 是記錄型別的名稱、label1 和 label2 是值的名稱 (稱為「標籤」(Label)),而 type1 和 type2 是這些值的型別。 member-list 則是型別之成員的選擇性清單。
下列是部分範例。
type Point = { x : float; y: float; z: float; }
type Customer = { First : string; Last: string; SSN: uint32; AccountNumber : uint32; }
當每個標籤位於不同行時,分號即為選擇性。
您可以在稱為「記錄運算式」(Record Expression) 的運算式中設定值。 編譯器會從使用的標籤推斷型別 (如果標籤足以和其他記錄型別的標籤區分)。 大括號 ({ }) 會括住記錄運算式。 下列程式碼顯示的記錄運算式會使用三個浮動項目來初始化記錄,這三個浮動項目的標籤分別為 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; }
可以針對記錄型別和類別型別來定義方法。
使用記錄運算式建立記錄
您可以使用記錄中所定義的標籤來初始化記錄。 執行這項工作的運算式稱為「記錄運算式」(Record Expression)。 請使用大括號括住記錄運算式,而且使用分號做為分隔符號。
下列範例顯示如何建立記錄。
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 }
這個形式的記錄運算式稱為「複製及更新記錄運算式」(Copy And Update Record Expression)。
記錄預設是不可變的,不過,使用複製和更新運算式,就可以輕鬆地建立修改過的記錄。 您也可以明確地指定可變的欄位。
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 方法)。