Operatori di diffusione record

Per condividere i campi senza unire tipi di modello distinti, usare gli spread di record, disponibili a partire da F# 11.

Utilizzare ...Type per contribuire alle dichiarazioni di campo in un nuovo record denominato o ...expression per contribuire ai valori di campo in un record denominato o anonimo. Il riutilizzo della dichiarazione e il mapping dei valori sono scelte indipendenti.

Comporre tipi di record

Per condividere i campi di connessione tra i tipi di configurazione, distribuiti Endpoint in una nuova dichiarazione di record:

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

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

ClientOptions ha Host, Porte TimeoutSeconds. È un tipo nominale distinto, non un Endpoint sottotipo.

Più origini e dichiarazioni esplicite possono essere visualizzate nella stessa definizione di tipo:

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

Partecipano solo i campi dei record. I metodi, le proprietà che non sono campi record e i membri statici non vengono copiati. Un'origine può essere un record denominato o un tipo di record anonimo, inclusa un'abbreviazione per un record anonimo. La destinazione di un tipo spread deve essere un tipo di record denominato.

Comporre i valori dei record

Copiare l'endpoint nelle opzioni client, quindi usare un record anonimo per sostituire l'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

Gli spread di espressioni accettano origini di record denominate o anonime e destinazioni, inclusi i record di struct. Ogni campo di destinazione deve ricevere un valore. Un'annotazione di destinazione esplicita è utile quando diversi tipi di record denominati condividono etichette.

Rafforzare il tipo di un campo

Una dichiarazione di campo successiva può sostituire il tipo di un campo spread. Ad esempio, rappresentare un identificatore analizzato con type CustomerId = CustomerId of System.Guid, quindi distinguere un record importato da uno con tale identificatore:

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 convalida il testo; lo spread copia solo i campi. CustomerId distingue gli identificatori da altri GUID senza imporre ulteriori regole.

Combinare modelli indipendenti

Comporre dichiarazioni da un record nominale curato e un tipo di record anonimo, quindi assemblare i valori da origini indipendenti:

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

Le origini denominate e anonime forniscono ProductCard; la scheda fornisce quindi lo stato di visualizzazione anonimo. CanOrder viene calcolato una sola volta, non ricalcolato quando una costruzione successiva modifica i conteggi.

Project in una destinazione nota

Un tipo di destinazione seleziona i campi da mantenere. Più fonti possono fornire sostituzioni:

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 e Port; production combina origini e sostituzioni esplicite. I campi espliciti assenti dalla destinazione rimangono errori.

Un record anonimo non annotato accetta tutti i campi forniti. Una destinazione denominata deve essere inferibile. Le origini sconosciute necessitano di annotazioni personalizzate, ad esempio (source: ClientOptions) in un parametro di funzione.

Ordine dei campi ed override

I contributi vengono letti da sinistra a destra. Per un nome di campo ripetuto, il contributo più a destra vince. La regola si applica sia alle dichiarazioni di campo nei tipi che ai valori dei campi nelle espressioni.

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

Dopo l'override di un tipo, specificare un valore del tipo di sostituzione. I campi espliciti ripetuti, ad esempio { Host = "first"; Host = "second" }, rimangono errori.

Per le dichiarazioni di tipo, viene visualizzato un campo vincente nella posizione del suo contributo vincente. I campi di un'origine mantengono l'ordine di origine. Questo ordine è importante per i costruttori e la conformità della firma.

Valutazione e copia

Le espressioni di origine distribuite vengono eseguite una sola volta, nell'ordine di origine, anche se un contributo successivo sostituisce ogni campo fornito:

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

Gli spread creano un nuovo record con copie di campo superficiali e le stesse conversioni delle assegnazioni esplicite. Gli oggetti modificabili a cui fanno riferimento i campi rimangono condivisi.

Gli aggiornamenti dei campi annidati funzionano con le distribuzioni, come fanno 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)

Un contributo successivo a Connection sostituisce un aggiornamento annidato precedente a tale campo. Posizionare sostituzioni intenzionali dopo le spread che dovrebbero sostituire.

Avvisi relativi ai campi ombreggiati

Una distribuzione successiva può annullare accidentalmente una scelta esplicita. Il compilatore segnala questi avvisi per impostazione predefinita:

Warning Meaning
FS3897 Un campo di un tipo distribuisce ombreggiate una dichiarazione di campo esplicita precedente.
FS3898 Un campo di un'espressione distribuisce ombreggiate un'associazione di campi esplicita precedente.

Ad esempio, { Host = "db.example.com"; ...local } accetta Host da local, non "db.example.com".

Per un controllo di altre sovrapposizioni, abilitare questi avvisi di consenso esplicito:

Warning Meaning
FS3905 Un tipo distribuisce ombreggiatura di un campo da una distribuzione di tipo precedente.
FS3906 Un campo esplicito ombreggiate un campo da una distribuzione precedente.
FS3907 Un'espressione distribuisce ombreggiate un campo da un'espressione precedente distribuita.
<WarnOn>$(WarnOn);3905;3906;3907</WarnOn>

Questi avvisi non modificano il comportamento più a destra.

Compatibilità tra rappresentazione e API

Il tipo distribuisce la mutabilità del campo di copia e gli attributi a livello di campo. Non copiano attributi a livello di tipo; L'attributo di destinazione determina [<Struct>] la relativa rappresentazione. Gli argomenti del tipo di origine devono soddisfare i vincoli generici dell'origine, inclusi i vincoli unit-of-measure.

Importante

Usare gli spread di tipo solo quando l'evoluzione condivisa è intenzionale. Una modifica del campo di origine può modificare i campi pubblici, il costruttore, gli attributi o i vincoli della destinazione senza apportare modifiche a tale destinazione.

Gli spread non ignorano i controlli di accessibilità. I record nullable non sono origini di diffusione valide; stabilire che un valore nullable è diverso da Null prima di usarlo. Le dipendenze cicliche tramite spread vengono rifiutate . I campi record ricorsivi ordinari non sono vietati solo perché la dichiarazione usa anche una distribuzione.

I tipi distribuisce funzionano nei .fsi file. Una firma può usare campi espliciti mentre l'implementazione usa spread o inverso, purché i campi risultanti e il relativo ordine siano d'accordo. Le virgolette contengono la costruzione di record elaborata, non un nodo spread.

Scegliere le distribuzioni o l'aggiornamento di copia e aggiornamento

Usare espressioni di copia e aggiornamento per un aggiornamento dello stesso tipo da un'origine. Usare le distribuzioni per combinare origini, proiettare in un tipo di record diverso o riutilizzare le dichiarazioni di campo. Non combinare with e ... nella stessa espressione di record.

Gli spread non si applicano a raccolte, espressioni di oggetti, oggetti arbitrari .NET o record C#. Non forniscono la sintassi del modello di record. I costruttori di record sono una funzionalità F# 11.2 separata.

Vedere anche