Anonyme Datensätze

Anonyme Datensätze sind einfache Aggregate benannter Werte, die vor der Verwendung nicht deklariert werden müssen. Sie können sie entweder als Struktur oder als Verweistypen deklarieren. Standardmäßig sind sie Verweistypen.

Syntax

Die folgenden Beispiele veranschaulichen die Syntax anonymer Datensätze. Elemente, die als [item] gekennzeichnet sind, sind optional.

// Construct an anonymous record
let value-name = [struct] {| Label1: Type1; Label2: Type2; ...|}

// Use an anonymous record as a type parameter
let value-name = Type-Name<[struct] {| Label1: Type1; Label2: Type2; ...|}>

// Define a parameter with an anonymous record as input
let function-name (arg-name: [struct] {| Label1: Type1; Label2: Type2; ...|}) ...

Grundlegende Verwendung

Anonyme Datensätze sollten am besten als F#-Datensatztypen betrachtet werden, die vor der Instanziierung nicht deklariert werden müssen.

Hier erfahren Sie beispielsweise, wie Sie mit einer Funktion interagieren können, die einen anonymen Datensatz generiert:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    {| Diameter = d; Area = a; Circumference = c |}

let r = 2.0
let stats = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
    r stats.Diameter stats.Area stats.Circumference

Im folgenden Beispiel wird das vorherige Beispiel mit einer printCircleStats-Funktion erweitert, die einen anonymen Datensatz als Eingabe annimmt:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    {| Diameter = d; Area = a; Circumference = c |}

let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

let r = 2.0
let stats = getCircleStats r
printCircleStats r stats

Beim Aufrufen von printCircleStats mit einem anonymen Datensatztyp, der nicht über dieselbe „Form“ wie der Eingabetyp verfügt, tritt ein Kompilierungsfehler auf:

printCircleStats r {| Diameter = 2.0; Area = 4.0; MyCircumference = 12.566371 |}
// Two anonymous record types have mismatched sets of field names
// '["Area"; "Circumference"; "Diameter"]' and '["Area"; "Diameter"; "MyCircumference"]'

Anonyme Strukturdatensätze

Anonyme Datensätze können auch als Struktur mit dem optionalen Schlüsselwort struct definiert werden. Im folgenden Beispiel wird das vorherige Beispiel erweitert, indem ein anonymer Strukturdatensatz erstellt und genutzt wird:

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    // Note that the keyword comes before the '{| |}' brace pair
    struct {| Area = a; Circumference = c; Diameter = d |}

// the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

let r = 2.0
let stats = getCircleStats r
printCircleStats r stats

Strukturrückschluss

Anonyme Strukturdatensätze ermöglichen auch einen „Strukturrückschluss“, bei dem Sie das Schlüsselwort struct auf der Aufrufsite nicht angeben müssen. In diesem Beispiel lassen Sie das Schlüsselwort struct beim Aufrufen von printCircleStats aus:


let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

printCircleStats r {| Area = 4.0; Circumference = 12.6; Diameter = 12.6 |}

Das umgekehrte Muster, bei dem struct angegeben wird, wenn der Eingabetyp kein anonymer Strukturdatensatz ist, kann nicht kompiliert werden.

Einbetten anonymer Datensätze in andere Typen

Es ist nützlich, diskriminierte Unions zu deklarieren, deren Fälle Datensätze sind. Wenn die Daten in den Datensätzen jedoch vom selben Typ wie die diskriminierte Union sind, müssen Sie alle Typen als gegenseitig rekursiv definieren. Durch die Verwendung anonymer Datensätze wird diese Einschränkung vermieden. Es folgt ein Beispiel für einen Typ und eine Funktion mit Musterabgleich:

type FullName = { FirstName: string; LastName: string }

// Note that using a named record for Manager and Executive would require mutually recursive definitions.
type Employee =
    | Engineer of FullName
    | Manager of {| Name: FullName; Reports: Employee list |}
    | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |}

let getFirstName e =
    match e with
    | Engineer fullName -> fullName.FirstName
    | Manager m -> m.Name.FirstName
    | Executive ex -> ex.Name.FirstName

Kopieren und Aktualisieren von Ausdrücken

Anonyme Datensätze unterstützen die Erstellung mit Kopieren und Aktualisieren von Ausdrücken. So können Sie beispielsweise eine neue Instanz eines anonymen Datensatzes erstellen, der die Daten eines vorhandenen Datensatzes kopiert:

let data = {| X = 1; Y = 2 |}
let data' = {| data with Y = 3 |}

Im Gegensatz zu benannten Datensätzen können Sie mit anonymen Datensätzen jedoch völlig unterschiedliche Formen mit dem Kopieren und Aktualisieren von Ausdrücken erstellen. Im folgenden Beispiel wird derselbe anonyme Datensatz aus dem vorherigen Beispiel verwendet und zu einem neuen anonymen Datensatz erweitert:

let data = {| X = 1; Y = 2 |}
let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |}

Es ist auch möglich, anonyme Datensätze aus Instanzen benannter Datensätze zu erstellen:

type R = { X: int }
let data = { X = 1 }
let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |}

Sie können auch Daten in und aus Verweis- und anonymen Strukturdatensätzen kopieren:

// Copy data from a reference record into a struct anonymous record
type R1 = { X: int }
let r1 = { X = 1 }

let data1 = struct {| r1 with Y = 1 |}

// Copy data from a struct record into a reference anonymous record
[<Struct>]
type R2 = { X: int }
let r2 = { X = 1 }

let data2 = {| r1 with Y = 1 |}

// Copy the reference anonymous record data into a struct anonymous record
let data3 = struct {| data2 with Z = r2.X |}

Eigenschaften anonymer Datensätze

Anonyme Datensätze weisen eine Reihe von Merkmalen auf, die für das vollständige Verständnis ihrer Verwendung unerlässlich sind.

Anonyme Datensätze sind nominal

Anonyme Datensätze sind Nominaltypen. Sie können als benannte Datensatztypen (die auch nominal sind) betrachtet werden, die keine Vorabdeklaration erfordern.

Betrachten Sie das folgende Beispiel mit zwei anonymen Datensatzdeklarationen:

let x = {| X = 1 |}
let y = {| Y = 1 |}

Die x- und y-Werte weisen unterschiedliche Typen auf und sind nicht miteinander kompatibel. Sie sind nicht gleichzusetzen und nicht vergleichbar. Um dies zu veranschaulichen, betrachten Sie einen gleichwertigen benannten Datensatz:

type X = { X: int }
type Y = { Y: int }

let x = { X = 1 }
let y = { Y = 1 }

Anonyme Datensätze unterscheiden sich nicht grundsätzlich im Vergleich zu ihren benannten Datensatzäquivalenten, wenn es um die Typäquivalenz oder den Vergleich geht.

Anonyme Datensätze verwenden strukturelle Gleichheit und Vergleiche

Wie Datensatztypen sind anonyme Datensätze strukturell gleichzusetzen und vergleichbar. Dies gilt nur, wenn alle konstituierenden Typen Gleichheit und Vergleich unterstützen, z. B. bei Datensatztypen. Um Gleichheit oder Vergleich zu unterstützen, müssen zwei anonyme Datensätze dieselbe „Form“ aufweisen.

{| a = 1+1 |} = {| a = 2 |} // true
{| a = 1+1 |} > {| a = 1 |} // true

// error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]'
{| a = 1 + 1 |} = {| a = 2;  b = 1|}

Anonyme Datensätze sind serialisierbar

Sie können anonyme Datensätze genauso serialisieren wie benannte Datensätze. Hier sehen Sie ein Beispiel für die Verwendung von Newtonsoft.Json:

open Newtonsoft.Json

let phillip' = {| name="Phillip"; age=28 |}
let philStr = JsonConvert.SerializeObject(phillip')

let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(philStr)
printfn $"Name: {phillip.name} Age: %d{phillip.age}"

Anonyme Datensätze sind nützlich, um einfache Daten über ein Netzwerk zu senden, ohne im Voraus eine Domäne für Ihre serialisierten/deserialisierten Typen definieren zu müssen.

Anonyme Datensätze arbeiten mit anonymen C#-Typen zusammen

Es ist möglich, eine .NET-API zu verwenden, die die Verwendung anonymer C#-Typen erfordert. Anonyme C#-Typen sind durch die Verwendung anonymer Datensätze trivial zu handhaben. Das folgende Beispiel zeigt, wie anonyme Datensätze zum Aufrufen einer LINQ-Überladung verwendet werden, die einen anonymen Typ erfordert:

open System.Linq

let names = [ "Ana"; "Felipe"; "Emilia"]
let nameGrouping = names.Select(fun n -> {| Name = n; FirstLetter = n[0] |})
for ng in nameGrouping do
    printfn $"{ng.Name} has first letter {ng.FirstLetter}"

Es gibt eine Vielzahl anderer APIs, die in .NET verwendet werden und die die Übergabe eines anonymen Typs erfordern. Anonyme Datensätze sind Ihr Tool für das Arbeiten mit ihnen.

Einschränkungen

Anonyme Datensätze weisen einige Einschränkungen bei der Verwendung auf. Einige sind in ihrem Design verankert, andere wiederum können geändert werden.

Einschränkungen beim Musterabgleich

Anonyme Datensätze unterstützen im Gegensatz zu benannten Datensätzen keinen Musterabgleich. Dafür gibt es drei Gründe:

  1. Ein Muster muss im Gegensatz zu benannten Datensatztypen jedes Feld eines anonymen Datensatzes berücksichtigen. Dies liegt daran, dass anonyme Datensätze keine strukturelle Untertypisierung unterstützen – es handelt sich um Nominaltypen.
  2. Aufgrund von (1) ist es nicht möglich, zusätzliche Muster in einem Musterabgleichausdruck zu verwenden, da jedes unterschiedliche Muster einen anderen anonymen Datensatztyp implizieren würde.
  3. Aufgrund von (2) ist jedes anonyme Datensatzmuster ausführlicher als die Verwendung der „Punkt“-Notation.

Es gibt einen offenen Sprachvorschlag, um Musterabgleich in eingeschränkten Kontexten zuzulassen.

Einschränkungen bei der Veränderbarkeit

Es ist derzeit nicht möglich, einen anonymen Datensatz mit mutable-Daten zu definieren. Es gibt einen offenen Sprachvorschlag, um veränderliche Daten zuzulassen.

Einschränkungen bei anonymen Strukturdatensätzen

Es ist nicht möglich, anonyme Strukturdatensätze als IsByRefLike oder IsReadOnly zu deklarieren. Es gibt einen offenen Sprachvorschlag für anonyme IsByRefLike- und IsReadOnly-Datensätze.