Record (F#)

I record rappresentano aggregazioni semplici di valori denominati, facoltativamente con membri. Possono essere struct o tipi di riferimento. Sono tipi di riferimento per impostazione predefinita.

Sintassi

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

Osservazioni:

Nella sintassi precedente, typename è il nome del tipo di record, label1 e label2 sono nomi di valori, definiti etichette, e type1 e type2 sono i tipi di questi valori. member-list è l'elenco facoltativo di membri per il tipo. È possibile usare l'attributo [<Struct>] per creare un record struct anziché un record che è un tipo riferimento.

Di seguito sono riportati alcuni esempi.

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

Quando ogni etichetta si trova su una riga separata, il punto e virgola è facoltativo.

È possibile impostare valori nelle espressioni note come espressioni di record. Il compilatore deduce il tipo dalle etichette usate (se le etichette sono sufficientemente distinte da quelle di altri tipi di record). Le parentesi graffe ({ }) racchiudono l'espressione di record. Il codice seguente illustra un'espressione di record che inizializza un record con tre elementi float con etichette xe zy .

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

Non utilizzare il formato abbreviato se potrebbe essere presente un altro tipo con le stesse etichette.

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 }

Le etichette del tipo dichiarato più di recente hanno la precedenza su quelle del tipo dichiarato in precedenza, quindi nell'esempio precedente viene mypoint3D dedotto come Point3D. È possibile specificare in modo esplicito il tipo di record, come nel codice seguente.

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

I metodi possono essere definiti per i tipi di record esattamente come per i tipi di classe.

Creazione di record tramite espressioni di record

È possibile inizializzare i record usando le etichette definite nel record. Espressione che esegue questa operazione viene definita espressione di record. Usare le parentesi graffe per racchiudere l'espressione di record e usare il punto e virgola come delimitatore.

Nell'esempio seguente viene illustrato come creare un record.

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

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

I punti e virgola dopo l'ultimo campo nell'espressione di record e nella definizione del tipo sono facoltativi, indipendentemente dal fatto che i campi siano tutti in una riga.

Quando si crea un record, è necessario specificare i valori per ogni campo. Non è possibile fare riferimento ai valori di altri campi nell'espressione di inizializzazione per qualsiasi campo.

Nel codice seguente il tipo di myRecord2 viene dedotto dai nomi dei campi. Facoltativamente, è possibile specificare il nome del tipo in modo esplicito.

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

Un'altra forma di costruzione di record può essere utile quando è necessario copiare un record esistente ed eventualmente modificare alcuni valori di campo. La riga di codice seguente illustra questa operazione.

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

Questa forma dell'espressione di record è denominata espressione di record di copia e aggiornamento.

I record non sono modificabili per impostazione predefinita; Tuttavia, è possibile creare facilmente record modificati usando un'espressione di copia e aggiornamento. È anche possibile specificare in modo esplicito un campo modificabile.

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

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

myCar.Odometer <- myCar.Odometer + 21

Non usare l'attributo DefaultValue con i campi record. Un approccio migliore consiste nel definire le istanze predefinite dei record con campi inizializzati sui valori predefiniti e quindi usare un'espressione di record di copia e aggiornamento per impostare i campi che differiscono dai valori predefiniti.

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

Creazione di record ricorsivi a vicenda

In alcuni casi durante la creazione di un record, può essere necessario dipendere da un altro tipo che si desidera definire in un secondo momento. Si tratta di un errore di compilazione a meno che non si definisci i tipi di record che si eseguano a vicenda.

La definizione di record ricorsivi a vicenda viene eseguita con la and parola chiave . In questo modo è possibile collegare 2 o più tipi di record.

Ad esempio, il codice seguente definisce un Person tipo e Address come ricorsivo a vicenda:

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

Per creare istanze di entrambe, eseguire le operazioni seguenti:

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

Se si definisse l'esempio precedente senza la and parola chiave , non verrà compilato. La and parola chiave è necessaria per le definizioni ricorsive a vicenda.

Criteri di ricerca con record

I record possono essere usati con criteri di ricerca. È possibile specificare alcuni campi in modo esplicito e specificare variabili per altri campi che verranno assegnati quando si verifica una corrispondenza. Questo aspetto è illustrato nell'esempio di codice seguente.

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 }

L'output di questo codice è il seguente.

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

Record e membri

È possibile specificare membri su record molto simili a quanto possibile con le classi. Non è disponibile alcun supporto per i campi. Un approccio comune consiste nel definire un Default membro statico per semplificare la costruzione di record:

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

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

let defaultPerson = Person.Default

Se si usa un identificatore self, tale identificatore fa riferimento all'istanza del record il cui membro viene chiamato:

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

Differenze tra record e classi

I campi dei record differiscono dai campi della classe in quanto vengono esposti automaticamente come proprietà e vengono usati nella creazione e nella copia dei record. La costruzione di record differisce anche dalla costruzione di classi. In un tipo di record non è possibile definire un costruttore. Si applica invece la sintassi di costruzione descritta in questo argomento. Le classi non hanno alcuna relazione diretta tra parametri del costruttore, campi e proprietà.

Analogamente ai tipi di unione e struttura, i record hanno una semantica di uguaglianza strutturale. Le classi hanno una semantica di uguaglianza dei riferimenti. Nell'esempio di codice seguente viene illustrata questa possibilità.

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

L'output di questo codice è il seguente:

The records are equal.

Se si scrive lo stesso codice con classi, i due oggetti classe sarebbero diversi perché i due valori rappresentano due oggetti nell'heap e solo gli indirizzi verranno confrontati (a meno che il tipo di classe non esegua l'override del System.Object.Equals metodo).

Se è necessaria l'uguaglianza dei riferimenti per i record, aggiungere l'attributo [<ReferenceEquality>] sopra il record.

Vedi anche