Compartilhar via


Registros (F#)

Os registros representam agregações simples de valores nomeados, opcionalmente com membros. Eles podem ser structs ou tipos de referência. Eles são tipos de referência por padrão.

Sintaxe

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

O accessibility modifier antes afeta typename a visibilidade de todo o tipo e é public por padrão. O segundo accessibility modifier afeta apenas o construtor e os campos.

Observações

Na sintaxe anterior, typename é o nome do tipo de registro, label1 e label2 são nomes de valores, conhecidos como rótulos, e type1 e type2 são os tipos desses valores. member-list é a lista opcional de membros para o tipo. Você pode usar o [<Struct>] atributo para criar um registro struct em vez de um registro que é um tipo de referência.

Estes são alguns exemplos:

// 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 cada rótulo está em uma linha separada, o ponto e vírgula é opcional.

Você pode definir valores em expressões conhecidas como expressões de registro. O compilador infere o tipo dos rótulos usados (se os rótulos forem suficientemente distintos dos de outros tipos de registro). Chaves ({ }) incluem a expressão de registro. O código a seguir mostra uma expressão de registro que inicializa um registro com três elementos float com rótulos xy e z.

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

Não use o formulário abreviado se houver outro tipo que também tenha os mesmos rótulos.

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 }

Os rótulos do tipo declarado mais recentemente têm precedência sobre os do tipo declarado anteriormente, portanto, no exemplo anterior, mypoint3D é inferido como Point3Dsendo . Você pode especificar explicitamente o tipo de registro, como no código a seguir.

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

Os métodos podem ser definidos para tipos de registro, assim como para tipos de classe.

Criando registros usando expressões de registro

Você pode inicializar registros usando os rótulos definidos no registro. Uma expressão que faz isso é conhecida como uma expressão de registro. Use chaves para colocar a expressão de registro e usar o ponto-e-vírgula como delimitador.

O exemplo a seguir mostra como criar um registro.

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

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

Os ponto e vírgula após o último campo na expressão de registro e na definição de tipo são opcionais, independentemente de todos os campos estarem em uma única linha.

Ao criar um registro, você deve fornecer valores para cada campo. Você não pode se referir aos valores de outros campos na expressão de inicialização para qualquer campo.

No código a seguir, o tipo de myRecord2 é inferido dos nomes dos campos. Opcionalmente, você pode especificar o nome do tipo explicitamente.

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

Outra forma de construção de registro pode ser útil quando você precisa copiar um registro existente e, possivelmente, alterar alguns dos valores de campo. A linha de código a seguir ilustra isso.

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

Essa forma da expressão de registro é chamada de expressão de registro de cópia e atualização.

Os registros são imutáveis por padrão; no entanto, você pode criar facilmente registros modificados usando uma expressão de cópia e atualização. Você também pode especificar explicitamente um campo mutável.

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

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

myCar.Odometer <- myCar.Odometer + 21

Não use o atributo DefaultValue com campos de registro. Uma abordagem melhor é definir instâncias padrão de registros com campos inicializados para valores padrão e, em seguida, usar uma expressão de registro de cópia e atualização para definir todos os campos que diferem dos valores padrão.

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

Criando registros mutuamente recursivos

Em algum momento, ao criar um registro, talvez você queira que ele dependa de outro tipo que você gostaria de definir posteriormente. Esse é um erro de compilação, a menos que você defina os tipos de registro para serem mutuamente recursivos.

A definição de registros mutuamente recursivos é feita com a and palavra-chave. Isso permite que você vincule dois ou mais tipos de registro juntos.

Por exemplo, o código a seguir define um tipo e um PersonAddress tipo como mutuamente recursivo:

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

Para criar instâncias de ambos, faça o seguinte:

// 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 você definisse o exemplo anterior sem a and palavra-chave, ele não seria compilado. A and palavra-chave é necessária para definições mutuamente recursivas.

Correspondência de padrões com registros

Os registros podem ser usados com correspondência de padrões. Você pode especificar alguns campos explicitamente e fornecer variáveis para outros campos que serão atribuídos quando ocorrer uma correspondência. O exemplo de código a seguir ilustra isso.

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 }

A saída desse código é a seguinte.

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

Registros e membros

Você pode especificar membros em registros como você pode com classes. Não há suporte para campos. Uma abordagem comum é definir um Default membro estático para facilitar a construção de registros:

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 você usar um auto-identificador, esse identificador se referirá à instância do registro cujo membro é chamado:

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

Modificadores de acessibilidade em registros

O código a seguir ilustra o uso de modificadores de acessibilidade. Há três arquivos no projeto: Module1.fs, Test1.fse Test2.fs. Um tipo de registro interno e um tipo de registro com um construtor privado são definidos no Módulo1.

// Module1.fs

module Module1

type internal internalRecd = { X: int }

type recdWithInternalCtor = private { Y: int }

Test1.fs No arquivo, o registro interno deve ser inicializado com o internal modificador de acesso, isso porque o nível de proteção do valor e do registro deve corresponder e ambos devem pertencer ao mesmo assembly.

// Test1.fs

module Test1

open Module1

let myInternalRecd1 = { X = 2 } // This line will cause a compiler error.

let internal myInternalRecd2 = { X = 4 } // This is OK

Test2.fs No arquivo, o registro com o construtor privado não pode ser inicializado diretamente devido ao nível de proteção do construtor.

// Test2.fs

module Test2

open Module1

let myRecdWithInternalCtor = { Y = 6 } // This line will cause a compiler error.

Para obter mais informações sobre modificadores de acessibilidade, consulte o artigo controle de acesso .

Diferenças entre registros e classes

Os campos de registro diferem dos campos de classe, pois são expostos automaticamente como propriedades e são usados na criação e cópia de registros. A construção de registros também difere da construção da classe. Em um tipo de registro, você não pode definir um construtor. Em vez disso, a sintaxe de construção descrita neste tópico se aplica. As classes não têm relação direta entre parâmetros, campos e propriedades do construtor.

Como tipos de união e estrutura, os registros têm semântica de igualdade estrutural. As classes têm semântica de igualdade de referência. O exemplo de código a seguir demonstra isso.

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

A saída desse código é a seguinte:

The records are equal.

Se você escrever o mesmo código com classes, os dois objetos de classe serão diferentes porque os dois valores representariam dois objetos no heap e apenas os endereços seriam comparados (a menos que o tipo de classe substitua o System.Object.Equals método).

Se você precisar de igualdade de referência para registros, adicione o atributo [<ReferenceEquality>] acima do registro.

Consulte também