紀錄差價運算人

若要共享欄位而不合併不同模型類型,請使用從 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 的獨立功能。

也請參閱