Delen via


Records (F#)

Records vertegenwoordigen eenvoudige aggregaties van benoemde waarden, optioneel met leden. Ze kunnen structs of verwijzingstypen zijn. Ze zijn standaard referentietypen.

Syntaxis

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

Opmerkingen

In de vorige syntaxis is typename de naam van het recordtype, label1 en label2 de namen van waarden, aangeduid als labels en type1 en type2 zijn de typen van deze waarden. ledenlijst is de optionele lijst met leden voor het type. U kunt het [<Struct>] kenmerk gebruiken om een struct-record te maken in plaats van een record die een verwijzingstype is.

Hier volgen enkele voorbeelden.

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

Wanneer elk label zich op een afzonderlijke regel bevindt, is de puntkomma optioneel.

U kunt waarden instellen in expressies die recordexpressies worden genoemd. De compiler bepaalt het type van de gebruikte labels (als de labels voldoende verschillen van die van andere recordtypen). Accolades ({ }) zetten de recordexpressie in. De volgende code toont een recordexpressie waarmee een record wordt geïnitialiseerd met drie float-elementen met labels xy en z.

let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }

Gebruik het verkorte formulier niet als er een ander type met dezelfde labels kan zijn.

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 }

De labels van het laatst gedeclareerde type hebben voorrang op die van het eerder gedeclareerde type, dus in het voorgaande voorbeeld mypoint3D wordt afgeleid.Point3D U kunt het recordtype expliciet opgeven, zoals in de volgende code.

let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }

Methoden kunnen worden gedefinieerd voor recordtypen, net als voor klassetypen.

Records maken met recordexpressies

U kunt records initialiseren met behulp van de labels die in de record zijn gedefinieerd. Een expressie die dit doet, wordt een recordexpressie genoemd. Gebruik accolades om de recordexpressie in te sluiten en gebruik de puntkomma als scheidingsteken.

In het volgende voorbeeld ziet u hoe u een record maakt.

type MyRecord = { X: int; Y: int; Z: int }

let myRecord1 = { X = 1; Y = 2; Z = 3 }

De puntkomma's na het laatste veld in de recordexpressie en in de typedefinitie zijn optioneel, ongeacht of de velden allemaal in één regel staan.

Wanneer u een record maakt, moet u waarden opgeven voor elk veld. U kunt niet verwijzen naar de waarden van andere velden in de initialisatie-expressie voor een veld.

In de volgende code wordt het type myRecord2 afgeleid van de namen van de velden. Desgewenst kunt u de typenaam expliciet opgeven.

let myRecord2 =
    { MyRecord.X = 1
      MyRecord.Y = 2
      MyRecord.Z = 3 }

Een andere vorm van recordconstructie kan handig zijn wanneer u een bestaande record moet kopiëren en mogelijk enkele veldwaarden moet wijzigen. De volgende coderegel illustreert dit.

let myRecord3 = { myRecord2 with Y = 100; Z = 2 }

Deze vorm van de recordexpressie wordt de expressie voor het kopiëren en bijwerken van de record genoemd.

Records zijn standaard onveranderbaar; U kunt echter eenvoudig gewijzigde records maken met behulp van een kopieer- en update-expressie. U kunt ook expliciet een veranderlijk veld opgeven.

type Car =
    { Make: string
      Model: string
      mutable Odometer: int }

let myCar =
    { Make = "Fabrikam"
      Model = "Coupe"
      Odometer = 108112 }

myCar.Odometer <- myCar.Odometer + 21

Gebruik het kenmerk DefaultValue niet met recordvelden. Een betere benadering is het definiëren van standaardexemplaren van records met velden die zijn geïnitialiseerd op standaardwaarden en vervolgens een kopieer- en updaterecordexpressie gebruiken om velden in te stellen die verschillen van de standaardwaarden.

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

Wederzijds recursieve records maken

Soms wilt u bij het maken van een record mogelijk afhankelijk zijn van een ander type dat u later wilt definiëren. Dit is een compileerfout, tenzij u de recordtypen definieert die wederzijds recursief zijn.

Het definiëren van wederzijds recursieve records wordt uitgevoerd met het and trefwoord. Hiermee kunt u twee of meer recordtypen aan elkaar koppelen.

Met de volgende code wordt bijvoorbeeld een Person en Address type gedefinieerd als wederzijds recursief:

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

Als u exemplaren van beide wilt maken, gaat u als volgt te werk:

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

Als u het vorige voorbeeld zonder het and trefwoord zou definiëren, zou het niet worden gecompileerd. Het and trefwoord is vereist voor recursieve definities.

Patroonkoppeling met records

Records kunnen worden gebruikt met patroonkoppeling. U kunt bepaalde velden expliciet opgeven en variabelen opgeven voor andere velden die worden toegewezen wanneer er een overeenkomst plaatsvindt. In het volgende codevoorbeeld ziet u dit.

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 }

De uitvoer van deze code is als volgt.

Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).

Records en leden

U kunt leden van records opgeven zoals u dat kunt met klassen. Er is geen ondersteuning voor velden. Een veelvoorkomende aanpak is het definiëren van een Default statisch lid voor eenvoudige recordconstructie:

type Person =
  { Name: string
    Age: int
    Address: string }

    static member Default =
        { Name = "Phillip"
          Age = 12
          Address = "123 happy fun street" }

let defaultPerson = Person.Default

Als u een self-id gebruikt, verwijst die id naar het exemplaar van de record waarvan het lid wordt aangeroepen:

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()

Verschillen tussen records en klassen

Recordvelden verschillen van klassevelden omdat ze automatisch worden weergegeven als eigenschappen en worden gebruikt bij het maken en kopiëren van records. Recordconstructie verschilt ook van klasseconstructie. In een recordtype kunt u geen constructor definiëren. In plaats daarvan is de syntaxis van de constructie die in dit onderwerp wordt beschreven, van toepassing. Klassen hebben geen directe relatie tussen constructorparameters, velden en eigenschappen.

Net als samenvoegings- en structuurtypen hebben records structurele semantiek voor gelijkheid. Klassen hebben verwijzingssemantiek voor gelijkheid. In het volgende codevoorbeeld ziet u dit.

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."

De uitvoer van deze code is als volgt:

The records are equal.

Als u dezelfde code schrijft met klassen, zijn de twee klasseobjecten ongelijk omdat de twee waarden twee objecten op de heap vertegenwoordigen en alleen de adressen worden vergeleken (tenzij het klassetype de System.Object.Equals methode overschrijft).

Als u referentie-gelijkheid voor records nodig hebt, voegt u het kenmerk [<ReferenceEquality>] boven de record toe.

Zie ook