Condividi tramite


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 dell'istanziazione.

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 con qualsiasi tipo di record anonimo che non abbia la stessa "forma" del tipo di input non verrà compilata.

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 registri 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 che effettuano il pattern matching su di esso.

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 usando 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 usano l'uguaglianza strutturale e il confronto

Analogamente ai tipi di record, i record anonimi sono strutturalmente equivalenti e confrontabili. Questo è vero solo se tutti i tipi costitutivi supportano l'uguaglianza e il confronto, proprio come avviene 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 che venga passato un tipo anonimo. I record anonimi sono il tuo strumento per lavorare con essi.

Limitazioni

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

Limitazioni con la corrispondenza di pattern

I record anonimi non supportano la corrispondenza di modelli, 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, ma richiedono la corrispondenza esatta dei campi.
  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 della 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. È disponibile un suggerimento di linguaggio aperto per IsByRefLike e IsReadOnly i record anonimi.