Operadores de propagación de registros

Para compartir campos sin combinar tipos de modelo distintos, use propagaciones de registros, disponibles a partir de F# 11.

Use ...Type para contribuir a declaraciones de campo a un nuevo registro con nombre o ...expression para contribuir a valores de campo a un registro con nombre o anónimo. La reutilización de declaraciones y la asignación de valores son opciones independientes.

Tipos de registro de redacción

Para compartir campos de conexión entre tipos de configuración, se distribuye Endpoint en una nueva declaración de registro:

type Endpoint = { Host: string; Port: int }

type ClientOptions =
    { ...Endpoint
      TimeoutSeconds: int }

ClientOptions tiene Host, Porty TimeoutSeconds. Es un tipo nominal distinto, no un Endpoint subtipo.

Pueden aparecer varios orígenes y declaraciones explícitas en la misma definición de tipo:

type Logging = { LogLevel: string }
type LoggedClientOptions = { ...ClientOptions; ...Logging }

Solo participan los campos de registro. Los métodos, las propiedades que no son campos de registro y los miembros estáticos no se copian. Un origen puede ser un registro con nombre o un tipo de registro anónimo, incluida una abreviatura de un registro anónimo. El destino de un tipo de propagación debe ser un tipo de registro con nombre.

Redacción de valores de registro

Copie el punto de conexión en las opciones de cliente y, a continuación, use un registro anónimo para reemplazar el host:

let local = { Host = "localhost"; Port = 5432 }
let options: ClientOptions = { ...local; TimeoutSeconds = 30 }

let remote = {| ...options; Host = "db.example.com" |}

printn $"{remote.Host}:{remote.Port}, timeout {remote.TimeoutSeconds}s"
// db.example.com:5432, timeout 30s

Las propagaciones de expresiones aceptan orígenes y destinos de registro anónimos o con nombre, incluidos los registros de estructura. Cada campo de destino debe recibir un valor. Una anotación de destino explícita es útil cuando varios tipos de registros con nombre comparten etiquetas.

Fortalecer el tipo de un campo

Una declaración de campo posterior puede reemplazar el tipo de un campo de propagación. Por ejemplo, represente un identificador analizado con type CustomerId = CustomerId of System.Guidy, a continuación, distinga un registro importado de uno con ese identificador:

type ImportedCustomer = { Name: string; Id: string }
type IdentifiedCustomer = { ...ImportedCustomer; Id: CustomerId }

let identify (source: ImportedCustomer) =
    match Guid.TryParse source.Id with
    | true, id -> Ok ({ ...source; Id = CustomerId id }: IdentifiedCustomer)
    | false, _ -> Error "Invalid customer identifier."

Guid.TryParse valida el texto; el propagado solo copia los campos. CustomerId distingue los identificadores de otros GUID sin imponer más reglas.

Combinar modelos independientes

Redactar declaraciones a partir de un registro nominal mantenido y un tipo de registro anónimo y, a continuación, ensamblar valores de orígenes independientes:

type PublicProductFields = { Name: string; UnitPrice: decimal }
type ProductRow = { ...PublicProductFields; InternalCost: decimal }
type StockCounts = {| InStock: int; Reserved: int |}

type ProductCard =
    { ...PublicProductFields
      ...StockCounts
      CanOrder: bool }
let product: ProductRow =
    { Name = "Notebook"; UnitPrice = 12.50m; InternalCost = 4m }
let stock: StockCounts = {| InStock = 8; Reserved = 2 |}

let card: ProductCard =
    { ...product; ...stock; CanOrder = stock.InStock > stock.Reserved }

let viewState = {| ...card; Selected = false |}
let selected = {| ...viewState; Selected = true |}

Los orígenes con nombre y anónimos proporcionan ProductCard; la tarjeta proporciona el estado de vista anónimo. CanOrder se calcula una vez, no se vuelve a calcular cuando una construcción posterior cambia los recuentos.

Project en un destino conocido

Un tipo de destino selecciona los campos que se van a conservar. Varios orígenes pueden proporcionar reemplazos:

let endpoint: Endpoint = { ...options }
let overrides = {| Host = "db.example.com"; Port = 5433 |}
let production: ClientOptions =
    { ...options; ...overrides; TimeoutSeconds = 60 }

printn $"{endpoint.Host}:{endpoint.Port}"
printn $"{production.Host}:{production.Port}, timeout {production.TimeoutSeconds}s"
// localhost:5432
// db.example.com:5433, timeout 60s

endpoint mantiene solo Host y Port; production combina orígenes y invalidaciones explícitas. Los campos explícitos ausentes del destino siguen siendo errores.

Un registro anónimo no anotado toma todos los campos aportados. Un destino con nombre debe ser inferible. Los orígenes desconocidos necesitan sus propias anotaciones, como (source: ClientOptions) en un parámetro de función.

Orden de campo e invalidaciones

Las contribuciones se leen de izquierda a derecha. Para un nombre de campo repetido, la contribución más a la derecha gana. La regla se aplica tanto a declaraciones de campo en tipos como a valores de campo en expresiones.

let laterHost = {| Host = "db.example.com" |}
let corrected: Endpoint = { ...local; ...laterHost; Port = 5433 }

Después de una invalidación de tipo, proporcione un valor del tipo de reemplazo. Los campos explícitos repetidos, como { Host = "first"; Host = "second" }, permanecen errores.

Para las declaraciones de tipo, un campo ganador aparece en la posición de su contribución ganadora. Los campos de un origen conservan su orden de origen. Este orden es importante para los constructores y la conformidad de firmas.

Evaluación y copia

Las expresiones de origen distribuidas se ejecutan una vez cada una, en orden de origen, incluso si una contribución posterior reemplaza a cada campo que proporcionan:

let visits = ResizeArray<string>()
let hostFrom source host =
    visits.Add source
    {| Host = host |}

let selected =
    {| ...hostFrom "first" "localhost"
       ...hostFrom "second" "db.example.com" |}

assert (selected.Host = "db.example.com")
assert (Seq.toList visits = [ "first"; "second" ])

Los propagados crean un nuevo registro con copias de campo superficial y las mismas conversiones que las asignaciones explícitas. Los objetos mutables a los que hacen referencia los campos permanecen compartidos.

Las actualizaciones de campos anidadas funcionan con propagaciones, como sucede con copy-and-update:

let original = {| Connection = local; Retries = 3 |}
let changed = {| ...original; Connection.Port = 5433 |}

assert (changed.Connection.Port = 5433)
assert (original.Connection.Port = 5432)

Una contribución posterior para Connection reemplazar una actualización anidada anterior a ese campo. Coloque invalidaciones deliberadas después de las propagaciones que deben invalidar.

Advertencias sobre campos sombreados

Una propagación posterior puede deshacer accidentalmente una opción explícita. El compilador notifica estas advertencias de forma predeterminada:

Warning Meaning
FS3897 Un campo de una propagación de tipos sombrea una declaración de campo explícita anterior.
FS3898 Un campo de una expresión propaga sombrea un enlace de campo explícito anterior.

Por ejemplo, { Host = "db.example.com"; ...local } toma Host de local, no "db.example.com".

Para una auditoría de otras superposiciones, habilite estas advertencias de participación:

Warning Meaning
FS3905 Un tipo propaga un campo a partir de una propagación de tipos anterior.
FS3906 Un campo explícito sombrea un campo de una propagación anterior.
FS3907 Una expresión propaga un campo de una extensión de expresión anterior.
<WarnOn>$(WarnOn);3905;3906;3907</WarnOn>

Estos avisos no cambian el comportamiento más a la derecha.

Compatibilidad de representaciones y API

El tipo propaga la mutabilidad del campo de copia y los atributos de nivel de campo. No copian atributos de nivel de tipo; El atributo propio [<Struct>] del destino determina su representación. Los argumentos de tipo de origen deben satisfacer las restricciones genéricas del origen, incluidas las restricciones de unidades de medida.

Importante

Use propagaciones de tipo solo cuando la evolución compartida sea intencionada. Un cambio de campo de origen puede modificar los campos públicos, el constructor, los atributos o las restricciones del destino sin editar en ese destino.

Los spreads no omiten las comprobaciones de accesibilidad. Los registros que aceptan valores NULL no son orígenes de propagación válidos; establece que un valor que acepta valores NULL no es NULL antes de usarlo. Se rechazan las dependencias cíclicas a través de propagaciones . Los campos de registro recursivos ordinarios no están prohibidos simplemente porque la declaración también usa una propagación.

Los tipos propagan el trabajo en .fsi archivos. Una firma puede usar campos explícitos mientras la implementación usa propagaciones o la inversa, siempre que los campos resultantes y su orden acepten. Las comillas contienen la construcción del registro elaborado, no un nodo de propagación.

Elegir spreads o copy-and-update

Use expresiones de copia y actualización para una actualización del mismo tipo desde un origen. Use propagaciones para combinar orígenes, proyectos en un tipo de registro diferente o reutilizar declaraciones de campo. No combine with ni ... en la misma expresión de registro.

Los propagados no se aplican a colecciones, expresiones de objeto, objetos de .NET arbitrarios ni registros de C#. No proporcionan sintaxis de patrón de registro. Los constructores de registros son una característica F# 11.2 independiente.

Consulte también