Operatoren voor recordspreiding

Als u velden wilt delen zonder afzonderlijke modeltypen samen te voegen, gebruikt u recordspreidingen, beschikbaar vanaf F# 11.

Hiermee ...Type kunt u velddeclaraties bijdragen aan een nieuwe benoemde record of ...expression veldwaarden bijdragen aan een benoemde of anonieme record. Declaratie hergebruik en waardetoewijzing zijn onafhankelijke keuzes.

Recordtypen opstellen

Als u verbindingsvelden wilt delen tussen configuratietypen, verspreidt Endpoint u zich over een nieuwe recorddeclaratie:

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

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

ClientOptions heeft Host, Porten TimeoutSeconds. Het is een uniek nominaal type, geen Endpoint subtype.

Meerdere bronnen en expliciete declaraties kunnen worden weergegeven in dezelfde typedefinitie:

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

Alleen recordvelden nemen deel. Methoden, eigenschappen die geen recordvelden zijn en statische leden worden niet gekopieerd. Een bron kan een benoemde record of een anoniem recordtype zijn, inclusief een afkorting voor een anonieme record. Het doel van een typespreiding moet een benoemd recordtype zijn.

Recordwaarden opstellen

Kopieer het eindpunt naar clientopties en gebruik vervolgens een anonieme record om de host te vervangen:

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

Expressies accepteren benoemde of anonieme recordbronnen en -bestemmingen, inclusief struct-records. Elk doelveld moet een waarde ontvangen. Een expliciete doelaantekening is handig wanneer meerdere benoemde recordtypen labels delen.

Het type veld versterken

Een latere velddeclaratie kan het type van een verspreid veld vervangen. Vertegenwoordig bijvoorbeeld een geparseerde id met type CustomerId = CustomerId of System.Guiden onderscheid vervolgens een geïmporteerde record van een record met die id:

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 valideert de tekst; de spread alleen kopieert velden. CustomerId onderscheidt id's van andere GUID's zonder verdere regels in te stellen.

Onafhankelijke modellen combineren

Stel declaraties op van een gecureerde nominale record en een anoniem recordtype en verzamel vervolgens waarden uit onafhankelijke bronnen:

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

De opgegeven en anonieme bronnen leveren ProductCard; de kaart levert vervolgens de status van anonieme weergave. CanOrder wordt eenmaal berekend, niet opnieuw berekend wanneer een latere constructie de aantallen wijzigt.

Project in een bekend doel

Een doeltype selecteert de velden die u wilt behouden. Meerdere bronnen kunnen vervangingen leveren:

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 houdt alleen Host en Port; production combineert bronnen en expliciete onderdrukkingen. Expliciete velden die ontbreken in het doel blijven fouten.

Een niet-geannoteerde anonieme record neemt alle bijgedragen velden in beslag. Een benoemd doel moet afleiden. Onbekende bronnen hebben hun eigen aantekeningen nodig, zoals (source: ClientOptions) op een functieparameter.

Veldvolgorde en onderdrukkingen

Bijdragen worden van links naar rechts gelezen. Voor een herhaalde veldnaam wint de meest rechtse bijdrage. De regel is zowel van toepassing op velddeclaraties in typen als op veldwaarden in expressies.

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

Nadat een type is overschreven, geeft u een waarde van het vervangende type op. Herhaalde expliciete velden, zoals { Host = "first"; Host = "second" }, blijven fouten.

Voor typedeclaraties wordt een winnende veld weergegeven op de positie van de winnende bijdrage. Velden van een bron behouden hun bronvolgorde. Deze volgorde is van belang voor constructors en handtekeningconformantie.

Evaluatie en kopiëren

Verspreide bronexpressies worden eenmaal uitgevoerd, in de bronvolgorde, zelfs als een latere bijdrage elk veld vervangt dat ze bieden:

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

Spreads maken een nieuwe record met ondiepe veldkopieën en dezelfde conversies als expliciete toewijzingen. Onveranderbare objecten waarnaar wordt verwezen door velden blijven gedeeld.

Geneste veldupdates werken met spreads, net als bij kopiëren en bijwerken:

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

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

Een latere bijdrage om een eerdere geneste update voor dat veld te Connection vervangen. Plaats opzettelijke onderdrukkingen na de spreads die ze moeten overschrijven.

Waarschuwingen over schaduwvelden

Een latere verspreiding kan per ongeluk een expliciete keuze ongedaan maken. De compiler rapporteert deze waarschuwingen standaard:

Warning Betekenis
FS3897 Een veld van een type verspreidt schaduwen een eerdere expliciete velddeclaratie.
FS3898 Een veld van een expressie verspreidt schaduwen van een eerder expliciete veldbinding.

Neemt bijvoorbeeld { Host = "db.example.com"; ...local }Host van local, niet "db.example.com".

Schakel deze opt-inwaarschuwingen in voor een controle van andere overlappingen:

Warning Betekenis
FS3905 Een type verspreidt schaduwen een veld van een eerdere typespreiding.
FS3906 Een expliciet veld schaduwt een veld van een eerdere verspreiding.
FS3907 Een expressie verspreidt schaduwen van een veld van een eerdere expressiespreiding.
<WarnOn>$(WarnOn);3905;3906;3907</WarnOn>

Deze kennisgevingen veranderen het gedrag van meest rechtse wins niet.

Compatibiliteit met weergave en API

Type spreidt de mutabiliteit van het veld en kenmerken op veldniveau. Ze kopiëren geen kenmerken op typeniveau; het eigen [<Struct>] kenmerk van het doel bepaalt de weergave. Argumenten voor brontypen moeten voldoen aan de algemene beperkingen van de bron, inclusief beperkingen voor maateenheden.

Important

Gebruik typespreidingen alleen wanneer gedeelde evolutie opzettelijk is. Een wijziging in het bronveld kan de openbare velden, constructor, kenmerken of beperkingen van de bestemming wijzigen zonder dat deze naar die bestemming hoeft te worden bewerkt.

Spreads omzeilen geen toegankelijkheidscontroles. Null-records zijn geen geldige verspreide bronnen; vaststellen dat een null-waarde niet-null is voordat u deze gebruikt. Cyclische afhankelijkheden via spreads worden geweigerd. Gewone recursieve recordvelden zijn niet alleen verboden omdat de declaratie ook gebruikmaakt van een spread.

Type verspreidt werk in .fsi bestanden. Een handtekening kan expliciete velden gebruiken terwijl de implementatie gebruikmaakt van spreads of omgekeerd, mits de resulterende velden en hun volgorde overeenkomen. Aanhalingstekens bevatten de uitgebreide recordconstructie, niet een verspreid knooppunt.

Spreads kiezen of kopiëren en bijwerken

Gebruik expressies voor kopiëren en bijwerken voor een update van hetzelfde type uit één bron. Gebruik spreads om bronnen te combineren, projecteren naar een ander recordtype of om velddeclaraties opnieuw te gebruiken. Combineer en ... in dezelfde recordexpressie nietwith.

Spreads zijn niet van toepassing op verzamelingen, objectexpressies, willekeurige .NET objecten of C#-records. Ze bieden geen recordpatroonsyntaxis. Recordconstructors zijn een afzonderlijke F# 11.2-functie.

Zie ook