Partilhar via


Registos Anónimos

Registros anônimos são simples agregados 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 do registro anônimo. Itens delimitados como [item] são 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; ...|}) ...

Utilização básica

Os registros anônimos são melhor pensados 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 expande o 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 não será compilado:

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 registos anónimos

Os registos 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 struct anonymous:

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 estrutura

Os registros anônimos Struct também permitem a "inferência de structness", onde você não precisa especificar a struct palavra-chave 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.

Incorporação de registos anónimos noutros tipos

É útil declarar sindicatos discriminados cujos casos são registrados. Mas se os dados nos registros forem do mesmo tipo que a união discriminada, você deve definir todos os tipos como mutuamente recursivos. A utilização de registos anónimos evita esta restrição. O que se segue é um exemplo de tipo e 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

Os registos anónimos suportam a 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 referência e estruturar registros anônimos:

// 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 registos anónimos

Os registos anónimos têm uma série de características que são essenciais para compreender plenamente como podem ser utilizados.

Os registos anónimos são nominais

Os registos anónimos são tipos nominais. Eles são melhor pensados como tipos de registro nomeados (que também são nominais) que não exigem uma declaração inicial.

Considere o exemplo a seguir com duas declarações de registro anônimo:

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

Os x valores e y têm tipos diferentes e não são compatíveis entre si. Não são equânimes e não são comparáveis. Para ilustrar isso, considere um equivalente de registro nomeado:

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

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

Não há nada inerentemente diferente nos registros anônimos quando comparados com seus equivalentes de registro nomeados quando se trata de equivalência de tipo ou comparação.

Os registos anónimos utilizam a igualdade estrutural e a comparação

Tal como os tipos de registo, os registos anónimos são estruturalmente equáveis e comparáveis. Isto só é verdade se todos os tipos constituintes apoiarem a igualdade e a comparação, como acontece com os tipos de registo. Para apoiar a igualdade ou a comparação, dois registos 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|}

Os registros anônimos são serializáveis

Você pode serializar registros anônimos da mesma forma que pode fazer 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}"

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

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

É possível usar uma API .NET que requer o uso de tipos anônimos C#. Os tipos anônimos 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 de passar em um tipo anônimo. Os registos anónimos são a sua ferramenta para trabalhar com eles.

Limitações

Os registos anónimos têm algumas restrições na sua utilização. Alguns são inerentes ao seu design, mas outros são passíveis de mudança.

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

Os registos anónimos não suportam a correspondência de padrões, ao contrário dos registos nomeados. Existem três razões:

  1. Um padrão teria que levar em conta todos os campos de um registro anônimo, ao contrário dos tipos de registro nomeados. Isto porque os registos anónimos não suportam subtipagem estrutural – são tipos nominais.
  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 com mutabilidade

Atualmente, não é possível definir um registo 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 como IsByRefLike ou IsReadOnly. Há uma sugestão de linguagem aberta para registros anônimos IsReadOnlyIsByRefLike.