Datensätze (F#)

Datensätze stellen einfache Aggregate benannter Werte dar, optional mit Membern. Sie können entweder Struktur- oder Referenztypen sein. Sie sind standardmäßig Referenztypen.

Syntax

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

Bemerkungen

In der vorherigen Syntax ist Der Typname der Name des Datensatztyps, bezeichnung1 und label2 sind Namen von Werten, die als Bezeichnungenbezeichnet werden, und Typ1 und Typ2 sind die Typen dieser Werte. member-list ist die optionale Liste der Member für den Typ. Sie können das [<Struct>]-Attribut verwenden, um einen Strukturdatensatz anstelle eines Datensatzes zu erstellen, der ein Verweistyp ist.

Hier einige Beispiele.

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

Wenn sich jede Beschriftung in einer separaten Zeile befindet, ist das Semikolon optional.

Sie können Werte in Ausdrücken festlegen, die als Datensatzausdrückebezeichnet werden. Der Compiler leitet den Typ von den verwendeten Bezeichnungen ab (wenn sich die Bezeichnungen ausreichend von denen anderer Datensatztypen unterscheiden). Geschweifte Klammern ({ }) schließen den Datensatzausdruck ein. Der folgende Code zeigt einen Datensatzausdruck, der einen Datensatz mit drei float-Elementen mit den Bezeichnungen x, y und zinitialisiert.

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

Verwenden Sie das gekürzte Formular nicht, wenn ein anderer Typ vorhanden sein könnte, der auch die gleichen Bezeichnungen aufweist.

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 }

Die Bezeichnungen des zuletzt deklarierten Typs haben Vorrang vor denen des zuvor deklarierten Typs, sodass im vorherigen Beispiel mypoint3D als Point3D abgeleitet wird. Sie können den Datensatztyp explizit wie im folgenden Code angeben.

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

Methoden können für Datensatztypen genauso wie für Klassentypen definiert werden.

Erstellen von Datensätzen mithilfe von Datensatzausdrücken

Sie können Datensätze initialisieren, indem Sie die Bezeichnungen verwenden, die im Datensatz definiert sind. Ein Ausdruck, der dies tut, wird als Datensatzausdruck bezeichnet. Verwenden Sie geschweifte Klammern, um den Datensatzausdruck einzuschließen und das Semikolon als Trennzeichen zu verwenden.

Das folgende Beispiel veranschaulicht das Erstellen eines Datensatzes.

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

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

Die Semikolons nach dem letzten Feld im Datensatzausdruck und in der Typdefinition sind optional, unabhängig davon, ob sich alle Felder in einer Zeile befinden.

Wenn Sie einen Datensatz erstellen, müssen Sie Werte für jedes Feld angeben. Sie können nicht auf die Werte anderer Felder im Initialisierungsausdruck für jedes Feld verweisen.

Im folgenden Code wird der Typ myRecord2 aus den Namen der Felder abgeleitet. Optional können Sie den Typnamen explizit angeben.

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

Eine andere Form der Datensatzerstellung kann nützlich sein, wenn Sie einen vorhandenen Datensatz kopieren und möglicherweise einige der Feldwerte ändern müssen. Die folgende Codezeile veranschaulicht dies.

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

Diese Form des Datensatzausdrucks wird als Kopier- und Aktualisierungsdatensatzausdruckbezeichnet.

Datensätze sind standardmäßig unveränderlich; Sie können jedoch auf einfache Weise geänderte Datensätze erstellen, indem Sie einen Kopier- und Aktualisierungsausdruck verwenden. Sie können auch explizit ein veränderbares Feld angeben.

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

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

myCar.Odometer <- myCar.Odometer + 21

Verwenden Sie das DefaultValue-Attribut nicht für Datensatzfelder. Ein besserer Ansatz besteht darin, Standardinstanzen von Datensätzen mit Feldern zu definieren, die für Standardwerte initialisiert werden, und dann einen Kopier- und Aktualisierungsdatensatzausdruck verwenden, um alle Felder festzulegen, die sich von den Standardwerten unterscheiden.

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

Erstellen von gegenseitig rekursiven Datensätzen

Einige Zeit beim Erstellen eines Datensatzes sollten Sie möglicherweise von einem anderen Typ abhängig sein, den Sie danach definieren möchten. Dies ist ein Kompilierungsfehler, es sei denn, Sie definieren die Datensatztypen, die gegenseitig rekursiv sein sollen.

Das Definieren sich gegenseitig rekursiver Datensätze erfolgt mit dem and-Schlüsselwort. Auf diese Weise können Sie 2 oder mehr Datensatztypen miteinander verknüpfen.

Der folgende Code definiert z. B. einen Person und Address einen Typ als gegenseitig rekursiv:

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

Um Instanzen von beiden zu erstellen, führen Sie folgendes aus:

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

Wenn Sie das vorherige Beispiel ohne Schlüsselwort and definieren möchten, würde es nicht kompiliert. Das and-Schlüsselwort ist für rekursive Definitionen erforderlich.

Musterabgleich mit Datensätzen

Datensätze können mit Musterabgleich verwendet werden. Sie können einige Felder explizit angeben und Variablen für andere Felder bereitstellen, die beim Auftreten einer Übereinstimmung zugewiesen werden. Dies wird im folgenden Codebeispiel veranschaulicht.

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 }

Dieser Code generiert folgende Ausgabe:

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

Datensätze und Members

Sie können Elemente in Datensätzen ähnlich wie bei Klassen angeben. Es gibt keine Unterstützung für Felder. Ein allgemeiner Ansatz besteht darin, ein Default statisches Element für die einfache Datensatzerstellung zu definieren:

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

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

let defaultPerson = Person.Default

Wenn Sie einen Selbstbezeichner verwenden, bezieht sich dieser Bezeichner auf die Instanz des Datensatzes, dessen Mitglied aufgerufen wird:

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

Unterschiede zwischen Datensätzen und Klassen

Datensatzfelder unterscheiden sich von Klassenfeldern, in denen sie automatisch als Eigenschaften verfügbar gemacht werden, und sie werden beim Erstellen und Kopieren von Datensätzen verwendet. Die Datensatzkonstruktion unterscheidet sich auch von der Klassenkonstruktion. In einem Datensatztyp können Sie keinen Konstruktor definieren. Stattdessen gilt die in diesem Thema beschriebene Konstruktionssyntax. Klassen weisen keine direkte Beziehung zwischen Konstruktorparametern, Feldern und Eigenschaften auf.

Wie Gewerkschafts- und Strukturtypen weisen Datensätze strukturelle Gleichheitssemantik auf. Klassen verfügen über Referenzgleichheitssemantik. Dies wird im folgenden Codebeispiel veranschaulicht.

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

Dieser Code generiert folgende Ausgabe:

The records are equal.

Wenn Sie denselben Code mit Klassen schreiben, sind die beiden Klassenobjekte ungleich, da die beiden Werte zwei Objekte im Heap darstellen und nur die Adressen verglichen werden (es sei denn, der Klassentyp überschreibt die System.Object.Equals-Methode).

Wenn Sie eine Referenzgleichheit für Datensätze benötigen, fügen Sie das Attribut [<ReferenceEquality>] oberhalb des Datensatzes hinzu.

Weitere Informationen