Record anonimi

I record anonimi sono semplici aggregazioni di valori denominati che non devono essere dichiarati prima dell'uso. È possibile dichiararli come struct o tipi di riferimento. Sono tipi di riferimento per impostazione predefinita.

Sintassi

Negli esempi seguenti viene illustrata la sintassi dei record anonimi. Gli elementi delimitati come [item] sono facoltativi.

// 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; ...|}) ...

Utilizzo di base

I record anonimi sono considerati come tipi di record F# che non devono essere dichiarati prima della creazione di istanze.

Ad esempio, qui come interagire con una funzione che produce un record anonimo:

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

Nell'esempio seguente viene espansa quella precedente con una printCircleStats funzione che accetta un record anonimo come input:

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

La chiamata printCircleStats a con qualsiasi tipo di record anonimo che non ha la stessa "forma" del tipo di input non riuscirà a compilare:

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"]'

Struct record anonimi

I record anonimi possono anche essere definiti come struct con la parola chiave facoltativa struct . L'esempio seguente aumenta quello precedente producendo e utilizzando un record anonimo struct:

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

Inferenza di structness

I record anonimi struct consentono anche l'inferenza "structness" in cui non è necessario specificare la struct parola chiave nel sito di chiamata. In questo esempio si elide la struct parola chiave quando si chiama printCircleStats:


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

Il modello inverso, che specifica struct quando il tipo di input non è un record anonimo di struct, non riuscirà a compilare.

Incorporamento di record anonimi all'interno di altri tipi

È utile dichiarare unioni discriminate i cui casi sono record. Tuttavia, se i dati nei record sono dello stesso tipo dell'unione discriminata, è necessario definire tutti i tipi come ricorsivi a vicenda. L'uso di record anonimi evita questa restrizione. Di seguito è riportato un tipo e una funzione di esempio corrispondenti al criterio:

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

Copiare e aggiornare le espressioni

I record anonimi supportano la costruzione con espressioni di copia e aggiornamento. Ecco ad esempio come creare una nuova istanza di un record anonimo che copia i dati di un record esistente:

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

Tuttavia, a differenza dei record denominati, i record anonimi consentono di creare moduli completamente diversi con espressioni di copia e aggiornamento. L'esempio seguente accetta lo stesso record anonimo dell'esempio precedente e lo espande in un nuovo record anonimo:

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

È anche possibile costruire record anonimi da istanze di record denominati:

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

È anche possibile copiare dati da e verso record anonimi di riferimento e struct:

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

Proprietà dei record anonimi

I record anonimi hanno una serie di caratteristiche essenziali per comprendere appieno come possono essere usati.

I record anonimi sono nominale

I record anonimi sono tipi nominale. Sono considerati come tipi di record denominati (che sono anche nominale) che non richiedono una dichiarazione iniziale.

Si consideri l'esempio seguente con due dichiarazioni di record anonimi:

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

I x valori e y hanno tipi diversi e non sono compatibili tra loro. Non sono equivaldi e non sono confrontabili. Per illustrare questo concetto, considerare un record denominato equivalente:

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

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

Non c'è nulla di intrinsecamente diverso rispetto ai record anonimi rispetto ai relativi equivalenti di record denominati quando riguarda l'equivalenza o il confronto dei tipi.

I record anonimi usano l'uguaglianza strutturale e il confronto

Analogamente ai tipi di record, i record anonimi sono strutturalmente equivalenti e confrontabili. Questo vale solo se tutti i tipi costitutivi supportano l'uguaglianza e il confronto, ad esempio con i tipi di record. Per supportare l'uguaglianza o il confronto, due record anonimi devono avere la stessa "forma".

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

I record anonimi sono serializzabili

È possibile serializzare record anonimi esattamente come è possibile con i record denominati. Di seguito è riportato un esempio che usa 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}"

I record anonimi sono utili per l'invio di dati leggeri in una rete senza la necessità di definire un dominio per i tipi serializzati/deserializzati in anticipo.

I record anonimi interagiscono con i tipi anonimi C#

È possibile usare un'API .NET che richiede l'uso di tipi anonimi C#. I tipi anonimi C# sono semplici da interagire con usando record anonimi. L'esempio seguente illustra come usare record anonimi per chiamare un overload LINQ che richiede un tipo anonimo:

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

Esistono numerose altre API usate in .NET che richiedono l'uso del passaggio di un tipo anonimo. I record anonimi sono lo strumento per usarli.

Limiti

I record anonimi hanno alcune restrizioni nell'utilizzo. Alcuni sono intrinseci alla loro progettazione, ma altri sono in grado di cambiare.

Limitazioni con criteri di ricerca

I record anonimi non supportano criteri di ricerca, a differenza dei record denominati. Esistono tre motivi:

  1. Un modello deve tenere conto di ogni campo di un record anonimo, a differenza dei tipi di record denominati. Ciò è dovuto al fatto che i record anonimi non supportano la sottotipizzazione strutturale, ovvero tipi nominale.
  2. A causa di (1), non è possibile avere modelli aggiuntivi in un'espressione di corrispondenza dei criteri, poiché ogni criterio distinto implica un tipo di record anonimo diverso.
  3. A causa di (2), qualsiasi modello di record anonimo sarebbe più dettagliato rispetto all'uso della notazione "dot".

È disponibile un suggerimento di linguaggio aperto per consentire la corrispondenza dei criteri in contesti limitati.

Limitazioni con mutabilità

Non è attualmente possibile definire un record anonimo con mutable i dati. È disponibile un suggerimento per la lingua aperta per consentire dati modificabili.

Limitazioni con i record anonimi dello struct

Non è possibile dichiarare record anonimi struct come IsByRefLike o IsReadOnly. Per i record anonimi e per i IsReadOnly record anonimi è disponibile un suggerimento per IsByRefLike la lingua aperta.