若要共享欄位而不合併不同模型類型,請使用從 F# 11 起提供的記錄散布。
用於 ...Type 將欄位聲明貢獻到新的命名紀錄,或 ...expression 將欄位值加入命名或匿名紀錄。 宣告重用與值映射是獨立的選擇。
組合記錄類型
若要跨組態類型共享連線欄位,請展開 Endpoint 新的紀錄宣告:
type Endpoint = { Host: string; Port: int }
type ClientOptions =
{ ...Endpoint
TimeoutSeconds: int }
ClientOptions 擁有 Host、 Port、 和 TimeoutSeconds。 它是一種獨特的名義型態,而非 Endpoint 子型。
多個來源與明確聲明可能出現在同一型別定義中:
type Logging = { LogLevel: string }
type LoggedClientOptions = { ...ClientOptions; ...Logging }
僅有紀錄參賽隊伍。 方法、非記錄欄位的屬性和靜態成員不會被複製。 來源可以是命名紀錄或匿名紀錄類型,包括匿名紀錄的縮寫。 類型擴散的目的地必須是命名的記錄類型。
組合唱片值
將端點複製到用戶端選項,然後使用匿名紀錄替換主機:
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
表達式展開接受命名或匿名的記錄來源與目的地,包括結構記錄。 每個目的欄位必須接收一個值。 當多個命名的記錄類型共享標籤時,明確的目標標註非常有用。
強化田的類型
後續的場宣告可以取代擴散場的類型。 例如,將一個解析過的識別碼表示為 type CustomerId = CustomerId of System.Guid,然後區分匯入紀錄與帶有該識別碼的紀錄:
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 驗證文本;散布只複製欄位。
CustomerId 在不施加額外規則的情況下,將識別碼與其他 GUID 區分開來。
結合獨立模型
從精選的名義紀錄和匿名紀錄類型組合宣告,然後從獨立來源組合數值:
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 |}
匿名來源提供 ProductCard;卡片則提供匿名觀點狀態。
CanOrder 只計算一次,當後續構造改變計數時不會重新計算。
Project 進入已知目標
目標類型會選擇要保留的欄位。 多個來源可以提供替換品:
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 僅 Host 保留 和 Port; production 結合來源與明確覆寫。 目標缺失的明確欄位仍為錯誤。
一個無註解的匿名記錄會接收所有貢獻欄位。 指定目標必須是可推理的。 未知來源需要自己的註解,例如 (source: ClientOptions) 函數參數。
場地順序與覆蓋
發言內容由左至右朗讀。 重複欄位名稱者,最 右邊的貢獻獲勝。 此規則同時適用於型別中的欄位宣告以及表達式中的欄位值。
let laterHost = {| Host = "db.example.com" |}
let corrected: Endpoint = { ...local; ...laterHost; Port = 5433 }
在型別覆寫後,提供替換型別的值。 重複出現的顯式欄位,如 { Host = "first"; Host = "second" },仍為錯誤。
對於類型宣告,勝出欄位會出現在其勝出貢獻的位置。 來源欄位會保留其來源順序。 此順序對建構者及簽名一致性很重要。
評價與抄襲
擴散來源表達式會依來源順序執行一次,即使後續貢獻會替換它們提供的所有欄位:
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" ])
跨頁會建立一個包含淺欄位副本的新記錄,且轉換與明確指派相同。 由欄位參照的可變物件仍保持共享。
巢狀欄位更新適用於擴散,就像複製與更新一樣:
let original = {| Connection = local; Retries = 3 |}
let changed = {| ...original; Connection.Port = 5433 |}
assert (changed.Connection.Port = 5433)
assert (original.Connection.Port = 5432)
後續的貢獻 Connection 取代了該欄位先前的巢狀更新。 在他們應該覆蓋的擴散後方放置刻意的覆蓋。
關於陰影田的警告
後期的牌陣可能會意外撤銷明確的選擇。 編譯器會預設回報以下警告:
| Warning | Meaning |
|---|---|
| FS3897 | 型別擴散中的欄位會遮蔽先前的明確欄位宣告。 |
| FS3898 | 表達式擴散中的場會影子先前的明確場綁定。 |
例如,取 { Host = "db.example.com"; ...local }Host 自 local,而非 "db.example.com"。
若要稽核其他重疊,請啟用以下 自願加入警告:
| Warning | Meaning |
|---|---|
| FS3905 | 類型擴散會遮蔽較早類型擴散的場。 |
| FS3906 | 明確場會影子前一攤牌的場。 |
| FS3907 | 一個表情擴散,陰影中一片先前表情的場域擴散。 |
<WarnOn>$(WarnOn);3905;3906;3907</WarnOn>
這些通知不會改變最右贏者的行為。
表示與 API 相容性
型別擴散、複製欄位可變性及欄位層級屬性。 它們不會複製類型層級的屬性;目的地自身 [<Struct>] 的屬性決定其表示方式。 來源型別參數必須滿足來源的通用約束條件,包括單位度量限制。
Important
只有在有意共享演化時才使用類型擴散。 來源欄位的變更可以改變目的地的公共欄位、建構子、屬性或約束,而無需對該目的地進行編輯。
Spreads 不會繞過無障礙檢查。 可空的紀錄不是有效的擴散來源;在使用該值之前,確定可空值為非空值。 透過擴散的週期性依賴性被拒絕。 一般遞迴記錄欄位並不會因為宣告也使用擴散而被禁止。
字體跨頁是用 .fsi 檔案處理的。 簽名可以使用明確欄位,而實作則使用擴散欄位,反之亦然,前提是所得欄位及其順序一致。 引號包含詳細的記錄建構,而非擴散節點。
選擇版面或複製更新
使用複製與更新表達式,從同一來源進行相同類型的更新。 使用跨頁來合併來源、投影到不同的記錄類型,或重複使用欄位宣告。 不要在同一個紀錄表達式中合併with...。
散布不適用於集合、物件表達式、任意的 .NET 物件或 C# 記錄。 他們沒有提供記錄模式的語法。 唱片製造器 是 F# 11.2 的獨立功能。