Compartilhar via


Registros anônimos

Registros anônimos são agregações simples de valores nomeados que não precisam ser declarados antes do uso. Você pode declará-los como structs ou tipos de referência. Eles são tipos de referência por padrão.

Sintaxe

Os exemplos a seguir demonstram a sintaxe de registro anônimo. Itens delimitados como [item] opcionais.

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

Uso Básico

Registros anônimos são mais bem considerados como tipos de registro F# que não precisam ser declarados antes da instanciação.

Por exemplo, aqui como você pode interagir com uma função que produz um registro anônimo:

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

O exemplo a seguir se expande no anterior com uma printCircleStats função que usa um registro anônimo como entrada:

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

Chamar printCircleStats com qualquer tipo de registro anônimo que não tenha a mesma "forma" que o tipo de entrada falhará ao compilar:

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

Estruturar registros anônimos

Registros anônimos também podem ser definidos como struct com a palavra-chave opcional struct . O exemplo a seguir aumenta o anterior produzindo e consumindo um registro anônimo 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

Inferência de structness

Os registros anônimos struct também permitem "inferência de structness" onde você não precisa especificar a palavra-chave struct no site de chamada. Neste exemplo, você elide a struct palavra-chave ao chamar 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 |}

O padrão inverso - especificando struct quando o tipo de entrada não é um registro anônimo struct - não será compilado.

Inserindo registros anônimos em outros tipos

É útil declarar uniões discriminados cujos casos são registros. Mas se os dados nos registros forem do mesmo tipo que a união discriminada, você deverá definir todos os tipos como mutuamente recursivos. O uso de registros anônimos evita essa restrição. O que se segue é um tipo de exemplo e uma função que o padrão corresponde sobre ele:

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

Copiar e atualizar expressões

Registros anônimos dão suporte à construção com expressões de cópia e atualização. Por exemplo, veja como você pode construir uma nova instância de um registro anônimo que copia os dados de um existente:

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

No entanto, ao contrário dos registros nomeados, os registros anônimos permitem que você construa formulários totalmente diferentes com expressões de cópia e atualização. O exemplo a seguir usa o mesmo registro anônimo do exemplo anterior e o expande para um novo registro anônimo:

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

Também é possível construir registros anônimos a partir de instâncias de registros nomeados:

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

Você também pode copiar dados de e para registros anônimos de referência 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 |}

Propriedades de registros anônimos

Registros anônimos têm várias características que são essenciais para entender completamente como eles podem ser usados.

Registros anônimos usam igualdade e comparação estruturais

Assim como os tipos de registro, os registros anônimos são estruturalmente equatáveis e comparáveis. Isso só será verdadeiro se todos os tipos constituintes suportarem igualdade e comparação, como ocorre com tipos de registro. Para dar suporte à igualdade ou à comparação, dois registros anônimos devem ter a mesma "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|}

Registros anônimos são serializáveis

Você pode serializar registros anônimos da mesma forma que pode com registros nomeados. Aqui está um exemplo usando 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}"

Registros anônimos são úteis para enviar dados leves em uma rede sem a necessidade de definir um domínio para seus tipos serializados/desserializados antecipadamente.

Registros anônimos interoperam com tipos anônimos em C#

É possível usar uma API .NET que requer o uso de tipos anônimos em C#. Tipos anônimos em C# são triviais para interoperar usando registros anônimos. O exemplo a seguir mostra como usar registros anônimos para chamar uma sobrecarga LINQ que requer um tipo anônimo:

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

Há uma infinidade de outras APIs usadas em todo o .NET que exigem o uso da passagem de um tipo anônimo. Registros anônimos são sua ferramenta para trabalhar com eles.

Limitações

Registros anônimos têm algumas restrições em seu uso. Alguns são inerentes ao seu design, mas outros são passíveis de mudança.

Limitações com correspondência de padrões

Registros anônimos não dão suporte à correspondência de padrões, ao contrário dos registros nomeados. Há três motivos:

  1. Um padrão teria que considerar cada campo de um registro anônimo, ao contrário dos tipos de registro nomeados. Isso ocorre porque os registros anônimos não dão suporte à subtipagem estrutural – eles exigem correspondência exata de campo.
  2. Devido a (1), não há capacidade de ter padrões adicionais em uma expressão de correspondência de padrão, pois cada padrão distinto implicaria um tipo de registro anônimo diferente.
  3. Devido a (2), qualquer padrão de registro anônimo seria mais detalhado do que o uso da notação "ponto".

Há uma sugestão de linguagem aberta para permitir a correspondência de padrões em contextos limitados.

Limitações da mutabilidade

No momento, não é possível definir um registro anônimo com mutable dados. Há uma sugestão de linguagem aberta para permitir dados mutáveis.

Limitações com registros anônimos struct

Não é possível declarar registros anônimos struct como IsByRefLike ou IsReadOnly. Há uma sugestão de linguagem aberta para IsByRefLike e IsReadOnly registros anônimos.