匿名記錄
匿名記錄是不需要在使用之前就宣告的具名值簡單彙總。 您可以將其宣告為結構或參考型別。 其預設為參考型別。
語法
下列範例示範匿名記錄語法。 分隔為 [item]
的項目是選擇性的。
// Construct an anonymous record
let value-name = [struct] {| Label1: Type1; Label2: Type2; ...|}
// Use an anonymous record as a type parameter
let value-name = Type-Name<[struct] {| Label1: Type1; Label2: Type2; ...|}>
// Define a parameter with an anonymous record as input
let function-name (arg-name: [struct] {| Label1: Type1; Label2: Type2; ...|}) ...
基本使用方式
最好將匿名記錄視為不需要在具現化之前宣告的 F# 記錄類型。
例如,您可以在這裡與產生匿名記錄的函式互動方式:
open System
let getCircleStats radius =
let d = radius * 2.0
let a = Math.PI * (radius ** 2.0)
let c = 2.0 * Math.PI * radius
{| Diameter = d; Area = a; Circumference = c |}
let r = 2.0
let stats = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
下列範例會使用以匿名記錄做為輸入的 printCircleStats
函式,展開前一個範例:
open System
let getCircleStats radius =
let d = radius * 2.0
let a = Math.PI * (radius ** 2.0)
let c = 2.0 * Math.PI * radius
{| Diameter = d; Area = a; Circumference = c |}
let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) =
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
let r = 2.0
let stats = getCircleStats r
printCircleStats r stats
使用與輸入類型沒有相同「圖形」的任何匿名記錄類型來呼叫 printCircleStats
,將會無法編譯:
printCircleStats r {| Diameter = 2.0; Area = 4.0; MyCircumference = 12.566371 |}
// Two anonymous record types have mismatched sets of field names
// '["Area"; "Circumference"; "Diameter"]' and '["Area"; "Diameter"; "MyCircumference"]'
結構匿名記錄
匿名記錄也可以定義為具有選擇性 struct
索引鍵的結構。 下列範例會產生及取用結構匿名記錄來增強前一個範例:
open System
let getCircleStats radius =
let d = radius * 2.0
let a = Math.PI * (radius ** 2.0)
let c = 2.0 * Math.PI * radius
// Note that the keyword comes before the '{| |}' brace pair
struct {| Area = a; Circumference = c; Diameter = d |}
// the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
let r = 2.0
let stats = getCircleStats r
printCircleStats r stats
結構推斷
結構匿名記錄也允許在呼叫位置指定 struct
索引鍵的「結構推斷」。 在此範例中,您會在呼叫 printCircleStats
時滑動 struct
索引鍵:
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
printCircleStats r {| Area = 4.0; Circumference = 12.6; Diameter = 12.6 |}
反向模式 (當輸入類型不是結構匿名記錄時指定 struct
) 將無法編譯。
在其他類型中內嵌匿名記錄
宣告案例為記錄的區分等位相當實用。 但是,如果記錄中的資料與區分等位的類型相同,您就必須將所有類型定義為相互遞迴。 使用匿名記錄可避免此限制。 以下是範例類型和模式與其相符的函式:
type FullName = { FirstName: string; LastName: string }
// Note that using a named record for Manager and Executive would require mutually recursive definitions.
type Employee =
| Engineer of FullName
| Manager of {| Name: FullName; Reports: Employee list |}
| Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |}
let getFirstName e =
match e with
| Engineer fullName -> fullName.FirstName
| Manager m -> m.Name.FirstName
| Executive ex -> ex.Name.FirstName
複製及更新運算式
匿名記錄支援使用複製及更新運算式進行建構。 例如,以下是建構匿名記錄的新執行個體來複製現有記錄資料的方式:
let data = {| X = 1; Y = 2 |}
let data' = {| data with Y = 3 |}
不過,與具名記錄不同,匿名記錄可讓您使用複製及更新運算式來建構完全不同的形式。 下列範例會採用上一個範例中相同的匿名記錄,並將其展開至新的匿名記錄:
let data = {| X = 1; Y = 2 |}
let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |}
也可以從具名記錄的執行個體建構匿名記錄:
type R = { X: int }
let data = { X = 1 }
let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |}
您也可以在參考與結構匿名記錄之間來回複製資料:
// Copy data from a reference record into a struct anonymous record
type R1 = { X: int }
let r1 = { X = 1 }
let data1 = struct {| r1 with Y = 1 |}
// Copy data from a struct record into a reference anonymous record
[<Struct>]
type R2 = { X: int }
let r2 = { X = 1 }
let data2 = {| r1 with Y = 1 |}
// Copy the reference anonymous record data into a struct anonymous record
let data3 = struct {| data2 with Z = r2.X |}
匿名記錄的屬性
匿名記錄有一些特性,對於充分了解其使用方式至關重要。
匿名記錄為名義性的
匿名記錄為名義性的類型。 最好將其視為不需要預先宣告的具名記錄型別 (這也是名義性的)。
請考慮使用下列兩個匿名記錄宣告的範例:
let x = {| X = 1 |}
let y = {| Y = 1 |}
x
和 y
值具有不同的類型,且彼此並不相容。 這兩個值不相等,且無法比較。 為說明這一點,請考慮具名記錄對等項目:
type X = { X: int }
type Y = { Y: int }
let x = { X = 1 }
let y = { Y = 1 }
在考量類型對等或比較時,相較於具名記錄對等項目,匿名記錄原本就沒有任何不同之處。
匿名記錄會使用結構對等和比較
如同記錄類型,匿名記錄在結構上對等且可比較的。 僅在所有組成類型都支援對等和比較 (例如記錄類型) 時,才適用這種情況。 若要支援對等或比較,兩個匿名記錄必須具有相同的「圖形」。
{| a = 1+1 |} = {| a = 2 |} // true
{| a = 1+1 |} > {| a = 1 |} // true
// error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]'
{| a = 1 + 1 |} = {| a = 2; b = 1|}
匿名記錄為可序列化的
您可以序列化匿名記錄,就如同使用具名記錄一樣。 以下是使用 Newtonsoft.Json 的範例:
open Newtonsoft.Json
let phillip' = {| name="Phillip"; age=28 |}
let philStr = JsonConvert.SerializeObject(phillip')
let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(philStr)
printfn $"Name: {phillip.name} Age: %d{phillip.age}"
匿名記錄適用於透過網路傳送輕量型資料,而不需要預先定義序列化/還原序列化類型的網域。
匿名記錄會與 C# 匿名型別交互操作
您可以使用需要使用 C# 匿名型別的 .NET API。 C# 匿名型別使用匿名記錄即可簡單進行交互操作。 下列範例示範如何使用匿名記錄來呼叫需要匿名型別的 LINQ 多載:
open System.Linq
let names = [ "Ana"; "Felipe"; "Emilia"]
let nameGrouping = names.Select(fun n -> {| Name = n; FirstLetter = n[0] |})
for ng in nameGrouping do
printfn $"{ng.Name} has first letter {ng.FirstLetter}"
.NET 中有許多其他 API 需要使用傳入匿名型別。 匿名記錄是您使用這些 API 的工具。
限制
匿名記錄的使用方式會有一些限制。 有些是其設計固有的,而有些則是可變更的。
模式比對的限制
不同於具名記錄,匿名記錄並不支援模式比對。 有三個理由:
- 不同於具名記錄類型,模式必須考慮匿名記錄的每個欄位。 這是因為匿名記錄不支援結構化子類型 – 其為名義類型。
- 由於 (1),無法在模式比對運算式中擁有額外的模式,因為每個相異模式都代表不同的匿名記錄類型。
- 由於 (2),任何匿名記錄模式都會比使用「點」標記法更為詳盡。
開放語言建議,可在有限的內容中允許模式比對。
可變動性的限制
目前無法定義包含 mutable
資料的匿名記錄。 開放語言建議可允許可變動的資料。
結構匿名記錄的限制
無法將結構匿名記錄宣告為 IsByRefLike
或 IsReadOnly
。 IsByRefLike
和 IsReadOnly
匿名記錄都有開放語言建議。