Merekam operator penyebaran

Untuk berbagi bidang tanpa menggabungkan jenis model yang berbeda, gunakan spread rekaman, yang tersedia dimulai dengan F# 11.

Gunakan ...Type untuk menyumbang deklarasi bidang ke rekaman bernama baru, atau ...expression untuk menyumbang nilai bidang ke rekaman bernama atau anonim. Penggunaan kembali deklarasi dan pemetaan nilai adalah pilihan independen.

Menyusun jenis rekaman

Untuk berbagi bidang koneksi di seluruh jenis konfigurasi, tersebar Endpoint ke dalam deklarasi rekaman baru:

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

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

ClientOptions memiliki Host, Port, dan TimeoutSeconds. Ini adalah jenis nominal yang berbeda, bukan Endpoint subjenis.

Beberapa sumber dan deklarasi eksplisit dapat muncul dalam definisi jenis yang sama:

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

Hanya bidang rekaman yang berpartisipasi. Metode, properti yang bukan bidang rekaman, dan anggota statis tidak disalin. Sumber dapat berupa rekaman bernama atau jenis catatan anonim, termasuk singkatan untuk rekaman anonim. Tujuan dari sebaran jenis harus berupa jenis rekaman bernama.

Menyusun nilai rekaman

Salin titik akhir ke opsi klien, lalu gunakan catatan anonim untuk mengganti 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

Spread ekspresi menerima sumber dan tujuan rekaman bernama atau anonim, termasuk rekaman struct. Setiap bidang tujuan harus menerima nilai. Anotasi target eksplisit berguna ketika beberapa label berbagi jenis rekaman bernama.

Memperkuat jenis bidang

Deklarasi bidang yang lebih baru dapat menggantikan jenis bidang spread. Misalnya, mewakili pengidentifikasi yang diurai dengan type CustomerId = CustomerId of System.Guid, lalu membedakan rekaman yang diimpor dari satu dengan pengidentifikasi tersebut:

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 memvalidasi teks; sebaran hanya menyalin bidang. CustomerId membedakan pengidentifikasi dari GUID lain tanpa memberlakukan aturan lebih lanjut.

Menggabungkan model independen

Buat deklarasi dari rekaman nominal yang dikumpulkan dan jenis catatan anonim, lalu rakit nilai dari sumber independen:

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

Pasokan ProductCardsumber bernama dan anonim ; kartu kemudian memasok status tampilan anonim. CanOrder dihitung sekali, tidak dihitung ulang ketika konstruksi nanti mengubah hitungan.

Project menjadi target yang diketahui

Jenis target memilih bidang yang akan disimpan. Beberapa sumber dapat menyediakan penggantian:

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 hanya Host menyimpan dan Port; production menggabungkan sumber dan penimpaan eksplisit. Bidang eksplisit tidak ada dari target tetap kesalahan.

Rekaman anonim yang tidak diannotasi mengambil semua bidang yang dikontribusikan. Target bernama harus dapat disimpulkan. Sumber yang tidak diketahui memerlukan anotasi mereka sendiri, seperti (source: ClientOptions) pada parameter fungsi.

Urutan dan penimpaan bidang

Kontribusi dibaca dari kiri ke kanan. Untuk nama bidang berulang, kontribusi paling kanan menang. Aturan ini menerapkan keduanya ke deklarasi bidang dalam jenis dan ke nilai bidang dalam ekspresi.

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

Setelah penimpaan jenis, berikan nilai jenis penggantian. Bidang eksplisit berulang, seperti { Host = "first"; Host = "second" }, tetap kesalahan.

Untuk deklarasi jenis, bidang yang menang muncul pada posisi kontribusi pemenangnya. Bidang dari sumber mempertahankan urutan sumbernya. Pesanan ini penting bagi konstruktor dan kesesuaian tanda tangan.

Evaluasi dan penyalinan

Ekspresi sumber spread berjalan sekali, dalam urutan sumber, bahkan jika kontribusi selanjutnya menggantikan setiap bidang yang mereka berikan:

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

Spread membuat rekaman baru dengan salinan bidang dangkal dan konversi yang sama dengan penugasan eksplisit. Objek yang dapat diubah yang dirujuk oleh bidang tetap dibagikan.

Pembaruan bidang berlapis berfungsi dengan spread, seperti yang dilakukan dengan salin dan perbarui:

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

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

Kontribusi selanjutnya untuk Connection menggantikan pembaruan berlapis sebelumnya ke bidang tersebut. Tempatkan penimpaan yang disederhanakan setelah penyebaran yang harus mereka ambil alih.

Peringatan tentang bidang ber bayangan

Penyebaran selanjutnya secara tidak sengaja dapat membatalkan pilihan eksplisit. Kompilator melaporkan peringatan ini secara default:

Warning Meaning
FS3897 Bidang dari sebaran jenis membayangi deklarasi bidang eksplisit sebelumnya.
FS3898 Bidang dari ekspresi menyebar membayangi pengikatan bidang eksplisit sebelumnya.

Misalnya, { Host = "db.example.com"; ...local } mengambil Host dari local, bukan "db.example.com".

Untuk audit tumpang tindih lainnya, aktifkan peringatan keikutsertaan ini:

Warning Meaning
FS3905 Jenis menyebar membayangi bidang dari penyebaran jenis sebelumnya.
FS3906 Bidang eksplisit membayangi bidang dari penyebaran sebelumnya.
FS3907 Ekspresi menyebar membayangi bidang dari sebaran ekspresi sebelumnya.
<WarnOn>$(WarnOn);3905;3906;3907</WarnOn>

Pemberitahuan ini tidak mengubah perilaku paling kanan-menang.

Representasi dan kompatibilitas API

Jenis menyebarkan mutabilitas bidang salin dan atribut tingkat bidang. Mereka tidak menyalin atribut tingkat jenis; atribut tujuan sendiri [<Struct>] menentukan representasinya. Argumen jenis sumber harus memenuhi batasan generik sumber, termasuk batasan unit-of-measure.

Important

Gunakan spread jenis hanya ketika evolusi bersama disengaja. Perubahan bidang sumber dapat mengubah bidang publik, konstruktor, atribut, atau batasan tujuan tanpa pengeditan ke tujuan tersebut.

Spread tidak melewati pemeriksaan aksesibilitas. Rekaman nullable bukan sumber spread yang valid; menetapkan bahwa nilai nullable bukan null sebelum menggunakannya. Dependensi siklik melalui spread ditolak. Bidang rekaman rekursif biasa tidak dilarang hanya karena deklarasi juga menggunakan spread.

Jenis penyebaran berfungsi dalam .fsi file. Tanda tangan dapat menggunakan bidang eksplisit saat implementasi menggunakan spread, atau sebaliknya, asalkan bidang yang dihasilkan dan pesanannya setuju. Kutipan berisi konstruksi rekaman yang dijabarkan, bukan simpul spread.

Pilih spread atau copy-and-update

Gunakan ekspresi salin dan perbarui untuk pembaruan jenis yang sama dari satu sumber. Gunakan spread untuk menggabungkan sumber, proyek ke jenis rekaman yang berbeda, atau gunakan kembali deklarasi bidang. Jangan gabungkan with dan ... dalam ekspresi rekaman yang sama.

Spread tidak berlaku untuk koleksi, ekspresi objek, objek .NET arbitrer, atau rekaman C#. Mereka tidak memberikan sintaks pola rekaman. Konstruktor rekaman adalah fitur F# 11.2 terpisah.

Baca juga