共用方式為


記錄 (F#)

記錄代表具名值的簡單匯總,選擇性地與成員。 它們可以是結構或參考型別。 它們預設為參考型別。

語法

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

之前typename的 會影響accessibility modifier整個類型的可見性,而且預設為。public 第二個 accessibility modifier 只影響建構函式和欄位。

備註

在上一個語法中, 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之 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 個或多個記錄類型連結在一起。

例如,下列程式代碼會將 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()

記錄上的協助工具修飾元

下列程式碼說明協助工具修飾元的使用。 專案中有三個檔案: Module1.fsTest1.fsTest2.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>]

另請參閱