毫无疑问,这应该是一个结构体。它是不可变的并且占用16个字节。从反汇编的角度看,这是一个引用类型:
type InputParam =
| RegionString of string
| RegionFloat of float32
还有这个引用类型:
type InputParam =
| RegionString of RegionString: string
| RegionFloat of RegionFloat: float32
两者在功能上完全相同。唯一的区别在于编译器如何命名它们。它们都创建一个名为“RegionString”的子类,但具有不同的属性名称--“RegionString.item”与“RegionString.RegionString”。
当您将第一个示例转换为结构时,它会放弃子类并尝试在记录上粘贴2个“item”属性,导致FS3204独特名称错误。
就性能而言,在组合这些微小类型时,您应该使用结构体。考虑这个示例脚本:
type Name = Name of string
let ReverseName (Name s) =
s.ToCharArray() |> Array.rev |> System.String |> Name
[<Struct>]
type StrName = StrName of string
let StrReverseName (StrName s) =
s.ToCharArray() |> Array.rev |> System.String |> StrName
#time
Array.init 10000000 (fun x -> Name (x.ToString()))
|> Array.map ReverseName
|> ignore
#time
#time
Array.init 10000000 (fun x -> StrName (x.ToString()))
|> Array.map StrReverseName
|> ignore
#time
sizeof<Name>
sizeof<StrName>
第一个将引用类型包装在引用类型中,导致性能损失翻倍。
Real: 00:00:04.637, CPU: 00:00:04.703, GC gen0: 340, gen1: 104, gen2: 7
...
Real: 00:00:02.620, CPU: 00:00:02.625, GC gen0: 257, gen1: 73, gen2: 1
...
val it : int = 8
val it : int = 8
功能域建模非常棒,但您必须记住这些具有相同的性能开销:
let c = CustomerID 5
let i = 5 :> obj
建议将16字节以下的任何不可变内容都定义为结构体。如果超过16字节,则需要考虑其行为。如果该内容被频繁传递,最好使用64位引用指针并承担引用开销。但是对于在组合类型或函数内部使用的内部数据,请坚持使用结构体。