Compartir a través de


Registros (F#)

Los registros representan agregados simples de valores con nombre, opcionalmente con miembros. Pueden ser estructuras o tipos de referencia. Son tipos de referencia de forma predeterminada.

Sintaxis

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

antes accessibility modifier de typename que afecte a la visibilidad de todo el tipo y es public de forma predeterminada. El segundo accessibility modifier solo afecta al constructor y a los campos.

Observaciones

En la sintaxis anterior, typename es el nombre del tipo de registro, label1 y label2 son nombres de valores, denominados etiquetas, y type1 y type2 son los tipos de estos valores. member-list es la lista opcional de miembros para el tipo. Puede usar el [<Struct>] atributo para crear un registro de estructura en lugar de un registro que sea un tipo de referencia.

A continuación se muestran algunos ejemplos.

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

Cuando cada etiqueta está en una línea independiente, el punto y coma es opcional.

Puede establecer valores en expresiones conocidas como expresiones de registro. El compilador deduce el tipo de las etiquetas usadas (si las etiquetas son lo suficientemente distintas de las de otros tipos de registro). Llaves ({ }) encierra la expresión de registro. El código siguiente muestra una expresión de registro que inicializa un registro con tres elementos float con etiquetas xy yz.

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

No use el formulario abreviado si podría haber otro tipo que también tenga las mismas etiquetas.

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 }

Las etiquetas del tipo declarado más recientemente tienen prioridad sobre las del tipo declarado anteriormente, por lo que, en el ejemplo anterior, mypoint3D se deduce que es Point3D. Puede especificar explícitamente el tipo de registro, como en el código siguiente.

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

Los métodos se pueden definir para los tipos de registro igual que para los tipos de clase.

Crear registros mediante expresiones de registro

Puede inicializar registros mediante las etiquetas definidas en el registro. Una expresión que hace esto se conoce como expresión de registro. Use llaves para incluir la expresión de registro y usar el punto y coma como delimitador.

En el ejemplo siguiente se muestra cómo crear un registro.

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

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

Los punto y coma después del último campo de la expresión de registro y en la definición de tipo son opcionales, independientemente de si los campos están todos en una línea.

Al crear un registro, debe proporcionar valores para cada campo. No puede hacer referencia a los valores de otros campos de la expresión de inicialización para ningún campo.

En el código siguiente, el tipo de myRecord2 se deduce de los nombres de los campos. Opcionalmente, puede especificar explícitamente el nombre de tipo.

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

Otra forma de construcción de registros puede ser útil cuando tiene que copiar un registro existente y, posiblemente, cambiar algunos de los valores de campo. La siguiente línea de código ilustra esto.

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

Esta forma de la expresión de registro se denomina expresión de registro copy y update.

Los registros son inmutables de forma predeterminada; sin embargo, puede crear fácilmente registros modificados mediante una expresión de copia y actualización. También puede especificar explícitamente un campo mutable.

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

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

myCar.Odometer <- myCar.Odometer + 21

No use el atributo DefaultValue con campos de registro. Un enfoque mejor consiste en definir instancias predeterminadas de registros con campos que se inicializan en valores predeterminados y, a continuación, usar una expresión de registro de copia y actualización para establecer los campos que difieren de los valores predeterminados.

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

Crear registros mutuamente recursivos

En algún momento al crear un registro, puede que desee que dependa de otro tipo que desee definir posteriormente. Se trata de un error de compilación a menos que defina los tipos de registro para que sean mutuamente recursivos.

La definición de registros mutuamente recursivos se realiza con la and palabra clave . Esto le permite vincular 2 o más tipos de registros juntos.

Por ejemplo, el código siguiente define un Person tipo y Address como recursivo mutuamente:

// 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 crear instancias de ambos, haga lo siguiente:

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

Si tuviera que definir el ejemplo anterior sin la and palabra clave , no se compilaría. La and palabra clave es necesaria para las definiciones mutuamente recursivas.

Coincidencia de patrones con registros

Los registros se pueden usar con coincidencia de patrones. Puede especificar algunos campos explícitamente y proporcionar variables para otros campos que se asignarán cuando se produzca una coincidencia. En el ejemplo de código siguiente se muestra esto.

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 }

La salida de este código es la siguiente.

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 y miembros

Puede especificar miembros en registros de forma muy similar a las clases. No hay compatibilidad con campos. Un enfoque común es definir un Default miembro estático para facilitar la construcción 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

Si usa un identificador propio, ese identificador hace referencia a la instancia del registro al que se llama:

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 accesibilidad en registros

En el código siguiente se muestra el uso de modificadores de accesibilidad. Hay tres archivos en el proyecto: Module1.fs, Test1.fsy Test2.fs. Un tipo de registro interno y un tipo de registro con un constructor privado se definen en Module1.

// Module1.fs

module Module1

type internal internalRecd = { X: int }

type recdWithInternalCtor = private { Y: int }

En el Test1.fs archivo, el registro interno debe inicializarse con el internal modificador de acceso, esto se debe a que el nivel de protección del valor y el registro deben coincidir, y ambos deben pertenecer al mismo ensamblado.

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

En el Test2.fs archivo, el registro con el constructor privado no se puede inicializar directamente debido al nivel de protección del constructor.

// Test2.fs

module Test2

open Module1

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

Para obtener más información sobre los modificadores de accesibilidad, consulte el artículo Control de acceso .

Diferencias entre registros y clases

Los campos de registro difieren de los campos de clase en que se exponen automáticamente como propiedades y se usan en la creación y copia de registros. La construcción de registros también difiere de la construcción de clases. En un tipo de registro, no se puede definir un constructor. En su lugar, se aplica la sintaxis de construcción descrita en este tema. Las clases no tienen ninguna relación directa entre los parámetros de constructor, los campos y las propiedades.

Al igual que los tipos de unión y estructura, los registros tienen semántica de igualdad estructural. Las clases tienen semántica de igualdad de referencia. En el ejemplo de código siguiente se muestra esto.

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

La salida de este código es la siguiente:

The records are equal.

Si escribe el mismo código con clases, los dos objetos de clase no serían iguales porque los dos valores representarían dos objetos en el montón y solo se compararían las direcciones (a menos que el tipo de clase invalide el System.Object.Equals método).

Si necesita igualdad de referencia para los registros, agregue el atributo [<ReferenceEquality>] encima del registro.

Consulte también