Záznamy (F#)

Záznamy představují jednoduché agregace pojmenovaných hodnot, volitelně se členy. Mohou to být buď struktury, nebo odkazové typy. Ve výchozím nastavení jsou to odkazové typy.

Syntax

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

Poznámky

V předchozí syntaxi je typename název typu záznamu, label1 a label2 jsou názvy hodnot, které se označují jako popisky a type1 a type2 jsou typy těchto hodnot. member-list je volitelný seznam členů pro typ. Atribut můžete [<Struct>] použít k vytvoření záznamu struktury místo záznamu, který je odkazový typ.

Dále je uvedeno několik příkladů.

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

Pokud je každý popisek na samostatném řádku, středník je volitelný.

Hodnoty můžete nastavit ve výrazech označované jako výrazy záznamů. Kompilátor odvodí typ z použitých popisků (pokud jsou popisky dostatečně odlišné od těch ostatních typů záznamů). Složené závorky ({ }) uzavřete výraz záznamu. Následující kód ukazuje výraz záznamu, který inicializuje záznam se třemi prvky float s popisky x a y z .

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

Pokud existuje jiný typ, který má také stejné popisky, nepoužívejte zkrácený tvar.

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

Popisky naposledy deklarovaného typu mají přednost před popisky dříve deklarovaného typu, takže v předchozím příkladu je odvozen jako mypoint3D Point3D . Typ záznamu můžete explicitně zadat jako v následujícím kódu.

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

Metody lze definovat pro typy záznamů stejně jako pro typy tříd.

Vytváření záznamů pomocí výrazů záznamů

Záznamy můžete inicializovat pomocí popisků definovaných v záznamu. Výraz, který to dělá, se označuje jako výraz záznamu. Pomocí složených závorek uzavřete výraz záznamu a jako oddělovač použijte středník.

Následující příklad ukazuje, jak vytvořit záznam.

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

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

Středníky za posledním polem ve výrazu záznamu a v definici typu jsou volitelné bez ohledu na to, jestli jsou všechna pole na jednom řádku.

Při vytváření záznamu musíte zadat hodnoty pro každé pole. Hodnoty jiných polí ve výrazu inicializace nelze odkazovat na žádné pole.

V následujícím kódu je typ myRecord2 odvozen z názvů polí. Volitelně můžete explicitně zadat název typu.

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

Jiná forma konstrukce záznamu může být užitečná, když potřebujete zkopírovat existující záznam a případně změnit některé hodnoty polí. Ilustruje to následující řádek kódu.

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

Tato forma výrazu záznamu se nazývá výraz kopírování a aktualizace záznamu.

Záznamy jsou ve výchozím nastavení neměnné. Upravené záznamy ale můžete snadno vytvořit pomocí výrazu kopírování a aktualizace. Můžete také explicitně zadat změnitelné pole.

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

let myCar = { Make = "Fabrikam"; Model = "Coupe"; Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21

Nepoužívejte atribut DefaultValue s poli záznamu. Lepším přístupem je definovat výchozí instance záznamů s poli, která jsou inicializována na výchozí hodnoty, a pak pomocí výrazu kopírování a aktualizace záznamů nastavit všechna pole, která se liší od výchozích hodnot.

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

Vytváření vzájemně rekurzivních záznamů

Někdy při vytváření záznamu můžete chtít, aby závisel na jiném typu, který chcete následně definovat. Jedná se o chybu kompilace, pokud nedefinujte typy záznamů, které se vzájemně rekurzivní.

Definování vzájemně rekurzivních záznamů se provádí pomocí klíčového and slova . Díky tomu můžete propojit dva nebo více typů záznamů.

Například následující kód definuje typ a Person Address jako vzájemně rekurzivní:

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

Pokud byste definujte předchozí příklad bez and klíčového slova , nezkompiluje se. Klíčové and slovo je vyžadováno pro vzájemně rekurzivní definice.

Porovnávání vzorů se záznamy

Záznamy lze použít s porovnáváním vzorů. Můžete explicitně zadat některá pole a zadat proměnné pro jiná pole, která budou přiřazena, když dojde ke shodě. Následující příklad kódu to dokládá.

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 }

Výstup tohoto kódu je následující.

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

Záznamy a členové

Členy u záznamů můžete zadat podobně jako u tříd. Pole se nepodporují. Běžným přístupem je definování Default statického členu pro snadnou konstrukci záznamů:

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

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

let defaultPerson = Person.Default

Pokud použijete identifikátor sebe sama, odkazuje tento identifikátor na instanci záznamu, jehož člen je volán:

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

Rozdíly mezi záznamy a třídami

Pole záznamu se liší od polí třídy v tom, že jsou automaticky zpřístupněna jako vlastnosti a používají se při vytváření a kopírování záznamů. Konstrukce záznamů se také liší od konstrukce tříd. V typu záznamu nelze definovat konstruktor. Místo toho se používá syntaxe konstrukce popsaná v tomto tématu. Třídy nemají žádnou přímou relaci mezi parametry konstruktoru, poli a vlastnostmi.

Podobně jako typy sjednocených a struktur mají záznamy strukturální sémantiku rovnosti. Třídy mají sémantiku rovnosti odkazů. Následující příklad kódu to ukazuje.

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

Výstup tohoto kódu je následující:

The records are equal.

Pokud napíšete stejný kód s třídami, dva objekty třídy by byly nerovné, protože tyto dvě hodnoty by představovaly dva objekty na haldě a pouze adresy by byly porovnány (pokud typ třídy nepřepíše System.Object.Equals metodu).

Pokud potřebujete rovnost odkazů pro záznamy, přidejte nad [<ReferenceEquality>] záznam atribut .

Viz také