共用方式為


記錄 (F#)

記錄表示具名值的簡單彙總,並選擇性地搭配成員。 它們可以是結構或參考類型。 根據預設其為參考類型。

語法

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

備註

在上面的語法中,typename 是記錄類型的名稱,label1label2 是值的名稱 (稱為標籤),而 type1type2 是這些值的類型。 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 }

當每一個標籤都在單獨的一行時,分號是可選用的。

您可以在稱為記錄運算式的運算式中設定值。 編譯器會從所使用的標籤 (如果這些標籤與其他記錄類型的標籤有足夠地不同) 中推斷類型。 大括號 ({ }) 可用來括住記錄運算式。 下列程式碼展示了一個記錄運算式,用於初始化一個具有三個浮點數元素 (標籤為 xyz) 的記錄。

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 個或多個的記錄類型連結在一起。

例如,下列程式碼將 PersonAddress 類型定義為相互遞迴:

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

另請參閱